Navigation

Compass Plugin Reference

Plugin Structure

A newly initialized plugin template has the following folder structure:

Note

The plugin source exists in the src directory.

my-plugin
├── README.md - Add documentation for your plugin here.
|
├── electron
│   └── renderer
│       └── index.js - This is where the standalone app is setup to run in Electron.
|
├── lib - Compiled sources for production get put here. Do not edit anything in this folder.
|
└── src
    ├── index.js - Entry point for plugins to register themselves to the app registry.
    ├── plugin.js - The root component for this plugin, connected to the store.
    ├── plugin.spec.js - Tests for the root connected component.
    ├── actions - Plugin actions go into this folder.
    ├── assets - Static assets (e.g. Compass styles).
    |
    ├── components - Plugin React components go into this folder.
    │   ├── my-plugin - Each component has its own directory with the following structure:
    │   │   ├── index.js - Export the component as a default export here.
    │   │   ├── my-plugin.jsx - React implementation of the component.
    │   │   ├── my-plugin.less - Styles for this component.
    │   │   ├── my-plugin.stories.js - React Storybook stories for this component.
    │   │   └── my-plugin.spec.js - Tests for this component.
    |   |
    │   └── sub-component - Same structure for sub components.
    │       ├── index.js
    │       ├── sub-component.jsx
    │       ├── sub-component.less
    │       ├── sub-component.stories.js
    │       └── sub-component.spec.js
    |
    └── stores - Plugin stores go in this folder.
        ├── index.js - Entry point for stores, declares named exports.
        ├── store.js - Implementation of the store.
        └── store.spec.js - Unit tests for this store.

Plugin Roles

The role of your plugin dictates where your plugin exists within the Compass UI.

The 5 available roles for a plugin are:

Instance.Tab

This role adds a new tab at the instance level. Instance tabs are accessible by clicking the Home Icon in the top-left corner of the MongoDB Compass window. Currently, the two Instance Tabs in MongoDB Compass are Databases and Performance.

../../_images/instance-tab.png

The Instance Tabs role supports the following options:

Field Type Required / Optional Description
Component String Required The react component which draws the UI.
Name String Required The name of the tab
Order Integer Optional The order this tab appears in the tab list, starting from 1.

Header.Item

Header items display global information shown on every page of MongoDB Compass. These items exist at the top of the application and are shown next to the hostname.

../../_images/header-item.png

Tip

Since these items are given high visual hierarchy, they should be universally relevant.

The available space for header items is limited. If you are looking to display larger blocks of information, you might consider waiting until the user presses a provided icon or button to reveal the item(s).

The Header Items role supports the following options:

Field Type Required / Optional Description
Component String Required The react component which draws the UI.
Name String Required The name of the tab
Alignment String Optional Choose right (default) or left to indicate how the heading will be aligned.
Order Integer Optional The order this item appears in the heading list from outside to inside, starting from 1.

Database.Tab

This role adds a new tab at the database level. The only database tab currently in MongoDB Compass is Collections, which displays the collections within the selected database.

../../_images/database-tab.png

The Database Tabs role supports the following options:

Field Type Required / Optional Description
Component String Required The react component which draws the UI.
Name String Required The name of the tab.
Order Integer Optional The order this tab appears in the tab list, starting from 1.

Collection.Tab

This role adds a new tab in the collection view. Currently, the following collection tabs exist within MongoDB Compass:

  • Documents
  • Schema
  • Explain Plan
  • Indexes
  • Validation
../../_images/collection-tab.png

The Collection Tabs role supports the following options:

Field Type Required / Optional Description
Component String Required The react component which draws the UI.
Name String Required The name of the tab.
Order Integer Optional The order this tab appears in the tab list, starting from 1.
minimumServerVersion Decimal Optional The minimum server version required to use the component.

CollectionHUD.Item

This role exists at the collection level, displaying information in the top-right corner of the collection GUI. CollectionHUD items display numerical information relevant to the collection currently being viewed by the user. Currently, MongoDB Compass uses this space to show a count, total size, and average size of documents and indexes in a collection.

../../_images/collection-hud.png

Tip

The information shown by this role is meant to be supplemental. It should not distract the user from the content of the collection tabs. As such, it is recommended to keep the information in this role text-based, and refrain from visual cues.

The CollectionHUD Items role supports the following options:

Field Type Required / Optional Description
Component String Required The react component which draws the UI.
Name String Required The name of the item.
Order Integer Optional The order of the item going from left to right, starting from 1.

Writing Your Plugin

MongoDB Compass plugins are written in ReactJS, which is comprised of stores, actions and components.

  • Stores keep track of state information and pass that information to components.
  • Components receive information from the store and update the user interface.
  • Actions are how components communicate with the store to trigger changes.

Stores

A store is responsible for storing and maintaining the state of the React/Flux application architecture used by MongoDB Compass plugins. The store responds to events and actions, resulting in state changes which are then reflected by the component’s view.

This data flow is shown in the following diagram:

../../_images/react-diagram.png

Stores listen to actions. Components subscribe to stores.

Note

For more information on stores, refer to the Redux documentation.

Defining Stores

Note

MongoDB Compass uses Reflux 0.4.1, so stores need to be defined using the Reflux 0.4.1 syntax. For details on creating data stores in Reflux 0.4.1, see the Reflux documentation.

Optional

You can use the Reflux State Mixin package to make the store behave more like a React component. For more information on this package, see the package documentation.

Stores typically listen to a set of actions defined in ./src/actions.

A store can only subscribe to other stores during execution of the onActivated method. Otherwise, the subscription may fail if the other store has not yet been loaded.

The onActivated method is called once all plugins are activated, so actions involving external stores should be called in this method.

For details on the activation stage of a plugin, see Activation.

Example

import Reflux from 'reflux';
import StateMixin from 'reflux-state-mixin';
import Actions from 'actions';

const MyStore = Reflux.createStore({

  mixins: [StateMixin.store],

  listenables: Actions,

  /**
   * return the initial store state
   */
  getInitialState: function() {
    return { connected: false };
  },

  /**
   * called when all plugins are activated. Subscribe to external stores here.
   */
  onActivated: function(appRegistry) {
    appRegistry.getStore('ExternalStore').listenTo(this.handleExternalChange.bind(this));
    appRegistry.on('data-service-connected', this.onConnected.bind(this));
  },

  /**
   * called when a connection has been established (or failed to establish).
   */
  onConnected: function(error, dataService) {
    if (!error) {
      dataService.command({ ping: 1 }, (err, result) => {
        if (!err) {
          this.setState({ connected: true });   // this triggers the store
        }
      });
    }
  },

  handleExternalChange(extState) {
    // Do something here.
  }
});

export default MyStore;
export { MyStore };

Registering Stores

If a plugin needs to use an external store or register a store for other plugins to use, you must add the target store to the application registry.

Example

import MyStore from 'stores';

const activate(appRegistry) => {
   appRegistry.registerStore('MyPlugin.MyStore', MyStore);
};

Actions

User inputs trigger actions. For example: button clicks, link clicks and text entry.

Defining Actions

For information on defining actions and code examples, see the RefluxJS Documentation.

Registering Actions

If a plugin needs to use an external action or register an action for other plugins to use, you must add the target action to the application registry

The common pattern is to define all the actions in a single object and register them all at once.

Example

const Actions = Reflux.createActions([
   'buttonClicked',
   'formSubmitted'
 ]);

 const activate(appRegistry) => {
   appRegistry.registerAction('MyPlugin.Actions', Actions);
 };

Listening to Actions

To ensure that the flow of data through the application happens in a single direction, stores are the only object type which should listen to actions. A store can listen to any plugin’s actions.

Example

const Actions = require('../actions');

const MyPluginStore = Reflux.createStore({

  init: function() {
    Actions.buttonClicked.listenTo(this.handleButtonClicked.bind(this));
  },

  onActivated: function(appRegistry) {
    const actions = appRegistry.getAction('MyPlugin.Actions');
    actions.formSubmitted.listenTo(this.handleFormSubmitted.bind(this));
  },

  handleButtonClicked: function() {
    // Do something
  },

  handleFormSubmitted: function() {
    // Do something
  }
});

Components

Components in a plugin make up the user interface.

Defining Components

Components are defined using ES6 classes and use the React prop-type module to validate properties.

User interface actions that require application state changes should fire Reflux actions. State changes force the component to re-render. Styles should be imported from a local .less file with the same filename as the component.

MongoDB Compass uses the Node package classnames to provide class names to components.

Example

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';

import styles from './my-component.less';

class MyComponent extends Component {

  static propTypes = {
    editable: PropTypes.bool.isRequired
  };

  constructor(props) {
    super(props);
    this.state = { active: true };
  }

  handleClick() {
    this.setState({ active: !this.state.active });
  }

  render() {
     return (
      <div className={classnames(styles.root)}>
        <h2 className={classnames(styles.title)}>My Component</h2>
         <button
          className={classnames(styles.button)}
           type="button"
          disabled={this.props.editable}
           onClick={this.handleClick.bind(this)}
         >
           {this.state.active ? "Active" : "Inactive"}
       </button>
      </div>
    );
   }
 }

 export default MyComponent;
 export { MyComponent };

For more information on components, refer to the ReactJS documentation.

Registering Components

If a plugin needs to use an external component or register a component for other plugins to use, you must add the target component to the application registry in ./src/index.js.

Example

import MyComponent from './plugin';

const activate(appRegistry) => {
  appRegistry.registerComponent('MyPlugin.MyComponent', MyComponent);
};

MongoDB Compass Application Registry

The application registry is the object that holds all of the plugin’s stores, actions, components and roles.

The registry is globally available to all plugins through the global.hadronApp.appRegistry object.

Lifecycle Hooks

The following sections outline the lifecycle stages of a MongoDB Compass Plugin in the context of the application registry:

Activation

When a plugin first loads, MongoDB Compass calls the activate method in the plugin’s root index.js file. This is where the plugin registers various objects with the application registry for other plugins to use.

The following is an example from the media player plugin found in this tutorial:

function activate(appRegistry) {
   // Register the MediaPlayerPlugin as a role in Compass
   //
   // Available roles are:
   //   - Instance.Tab
   //   - Database.Tab
   //   - Collection.Tab
   //   - CollectionHUD.Item
   //   - Header.Item

   appRegistry.registerRole('Collection.Tab', ROLE);
   appRegistry.registerAction('MediaPlayer.Actions', MediaPlayerActions);
   appRegistry.registerStore('MediaPlayer.Store', MediaPlayerStore);
}

When plugin activation is completed, the application registry is also passed to any store in the registry containing an onActivated method. This guarantees that all pieces of the plugin have been registered before any plugin tried to pull another item from the registry.

Example

const MyPluginStore = Reflux.createStore({

   onActivated: function(appRegistry) {
     // Do potential stuff here.
   }
 });

Everything registered in the application registry is available for use by other plugins. For example, a plugin could listen for query changes and perform some action in response:

const MyPluginStore = Reflux.createStore({

   onActivated: function(appRegistry) {
     appRegistry.getStore('Query.ChangedStore').listenTo(this.handleQueryChange.bind(this));
   },

   handleQueryChange: function(query) {
      debug(query.filter);
      debug(query.project);
      debug(query.sort);
      debug(query.skip);
      debug(query.limit);
    }
});

Connection

Plugins can also access the connection lifecycle to know when the data service has established a connection to the server and is ready to accept requests. Plugins that access the data service directly need to implement this method.

Example

const PingStore = Reflux.createStore({

   onActivated(appRegistry) {
     appRegistry.on('data-service-connected', this.onConnected.bind(this));
   },

   onConnected: function((error, dataService)) {
     if (!error) {
       dataService.command({ ping: 1 }, (err, result) {
         if (!err) {
           this.trigger(result);
         }
       });
     }
   },
});

Query Changes

The onQueryChanged method is called on all registered stores when the MongoDB Compass query has been changed and executed. The query object passed to the method includes the following fields:

  • Filter
  • Project
  • Sort
  • Skip
  • Limit

For details on these fields, see Query Bar.

Example

const QueryListenerStore = Reflux.createStore({

   onActivated(appRegistry) {
     appRegistry.on('query-applied', this.onQueryChanged.bind(this));
   },

   onQueryChanged: function(query) {
     const projection = query.project;
     const sort = query.sort;
     ...
   }
});

Namespace Changes

Plugins that need to be informed of namespace changes can implement namespace lifecycle methods on stores that are registered with the application registry. The full namespace is passed to the function.

Example

const NameStore = Reflux.createStore({

   onCollectionChanged: function(ns) {
     this.setState({'namespace': ns});
   },
   onDatabaseChanged: function(ns) {
     this.setState({'namespace': ns});
   }
});

Custom Assets

To add a custom asset such as an image to your MongoDB Compass plugin, place the asset into the src/assets/images/ directory. You can display the asset in a component using the component’s render method.

For example, suppose you have a mongodb-logo.png image file in your src/assets/images/ directory. The following component code displays the image:

const src = require('../../assets/images/mongodb-logo.png');

render() {
   return (
      <img src={src} />
   )
}

Styling

With the new web-pack enabled plugin model (v1.0.0+), MongoDB Compass plugins implement CSS Modules and Less syntax to add styling to components.

Styles are scoped locally to a component. Style definitions exist directly next to a component’s implementation. See the following file structure for details:

components
└── my-component
     ├── index.js
     ├── my-component.jsx - React implementation of the component.
     ├── my-component.less - Styles for this component.
     ├── my-component.stories.js
     └── my-component.spec.js

For a component with the name MyComponent, the directoy is called my-component, and the style file inside this directory is my-component.less.

Note

See the structure of a plugin to view the full plugin folder structure.

Example

Consider the following implementation of a component called MyComponent:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';

import styles from './my-component.less';

class MyComponent extends Component {

  render() {
    return (
      <div className={classnames(styles.root)}>
        <h2 className={classnames(styles.title)}>This is a title.</h2>
      </div>
    );
  }
}

export default MyComponent;
export { MyComponent };

This component might have styles defined in my-component.less in the following manner:

.root {
   background: #0e83cd;
   color: #fff;
   height: 100vh;
   padding: 2.4rem;
 }

 .title {
   margin-top: 0;
}

Notice how the style file is imported at the top of the implementation file. This root and title elements are then accessible as keys in the styles object in the .less file.

Together with the classnames helper package, these keys can be passed as class names to the DOM elements as in the following example:

<div className={classnames(styles.title)}>...</div>

This code generates a DOM element with the following style class:

<div class=".MyPlugin_my-component-title__2Nklg">...</div>

Notice that this element has the plugin name prefixed, followed by the block component (in this case my-component), then the element (in this case -title), and finally a unique hash suffix to guarantee uniqueness. This hash guarantees uniqueness across different plugins.

Note

In general, developers do not have to worry about the generated class name.

Using Classnames for Cleaner Class Name Construction

The classnames package allows elegant string construction for complex class names.

To see how complex dynamic class names can be generated in ReactJS, see this example.

Running Your Plugin

Run the following command from your plugin’s root directory to build the plugin:

npm run compile

Launch Compass to view your plugin.