node package manager
Easy collaboration. Discover, share, and reuse code in your team. Create a free org »

uicore

Presence Insights UI

Build Status

This project contains the web application for interacting with presence insights. The application is organized as an AngularJS application, but should be flexible enough to incorporate other desired libraries.

Client dependencies are managed through Bower and development dependencies are managed through npm. Grunt is used as the task runner. Some of the primary tasks run during development are:

  • grunt serve [--open <tenantId>] - Starts a development server with the application. Source files are watched for changes and will force additional tasks to be run that trigger things like LESS compilation, automatic page refreshes, or linting. The --open parameter will open a browser to the specified tenant.
  • grunt build - Performs a build of our application. It will add all angular templates to the module's template cache, to reduce the number of downloads. It also concatenates and uglifies all JavaScript, HTML, and Stylesheets to produce a streamlined application.
  • npm run lint - Runs our linting/style checking
  • grunt jshint - DEPRECATED: Lints the JavaScript files in the project
  • grunt jscs - DEPRECATED: Checks the style of your JavaScript so that it conforms to common conventions.

Getting started

Setting up the Presence Insights Project

  1. Instead of cloning this repository directly, you will want to clone https://github.ibm.com/PresenceInsights/presence-insights.git

     git clone git@github.ibm.com:PresenceInsights/presence-insights.git
    
  2. Set up the presence-insights project

     cd presence-insights
     git checkout dev
     npm install
     grunt setup
    
  3. Check-out the dev branch of all of the children repos

     grunt git:checkout:dev
    
  4. Run all of the microservices with grunt serve

Setting up the UI

The UI has some additional setup steps after setting up the main project

  1. With the other microservices running from the last step, open another terminal window/tab

  2. Change to the UI directory cd src/ui/ui-core

  3. Install the module and bower dependencies

     npm install
    
  4. Start the UI server with grunt serve. grunt serve --open [tenantID] will open a browser with the specified tenant.

  5. Navigate to http://localhost:9000/piui/v1/tenants/cz4g054z, which is a default development tenant. You will need to log-in with your Bluemix credentials.

A Bit About webpack

The UI uses webpack as its module bundler. It allows us to write modular code that can depend on source written using CommonJS or AMD styles. The entry point for the project, from which all of our bundles are derived, is app/scripts/app.js. From here, our angular modules are required, which will then bring in the controllers, services, and directives that belong to those modules. webpack will then bundle up the dependencies into chunks in our application's bundle. Since we rarely update our 3rd-party dependencies, we've broken these out into a separate vendor bundle. Browsers can then rely on this cached version of the bundle instead of downloading one large bundle every time our application code changes.

There are two webpack tasks in our Gruntfile: webpack:dev and webpack:dist. webpack:dev, which is run as part of grunt serve, will watch for changes to resources in the dependency graph. When a file changes, it will rebuild that chunk in the bundle, and when the browser is refreshed, it will have that latest change. webpack is pretty good about changing only what is necessary, so the performance impact should be minimal. webpack:dev also includes source maps to aid in debugging; otherwise, you would be debugging the bundle in its entirety, which would be pretty annoying. Since we don't want these bundles polluting our source tree, they get put into .tmp, which our dev server is capable of serving resources from (if you were curious, this is were the Sass files compile to as well).

webpack:dist is run as part of grunt build. This task builds bundles much like webpack:dev; however, it does some extra work to produce hashed, uglified, and gzipped bundles. It also doesn't produce source maps as they're only really important to us.

A lot of the common definitions for these tasks are defined in webpack.config.js, with some additional, production-specific configuration in webpack.dist.js. webpack.config.js defines what goes into our vendor bundle, maps module names to the resource (e.g., require('c3') will get the resource bower_components/c3/c3.js), defines our loaders, and builds our bundles list.

Bundles

webpack.config.js has a custom plugin that maps bundle names to the generated source file and outputs the result as bundles.json. We can load the mappings in our server, and then when the dashboard jade template is rendered, it will have the proper reference to the scripts.

app.js is our application code. It starts from the entry point of app/scripts/app.js. From there, we require each of the angular modules that we want to make use of in the UI. Each of these modules may have dependencies on other angular modules. And so each of those is responsible for requiring those modules. For the project, we're using CommonJS syntax for requiring and declaring modules. A rough idea of how we do this is:

var angular = require('angular');
angular.module('piDashboard', [require('./chart/charts'), require('./services/services'), ...]).config(...);

Each of the angular modules that we create exports the module's name as the default value. As an example:

// charts/charts.js 
module.exports = require('angular').module('pi.charts', []).name;

vendor.js is our 3rd-party library code. Which source files end up there are defined in webpack.config.js. Since these dependencies rarely change, they're put into this bundle to take advantage of browser caching.

commons.js is the common webpack code, things like the module loader and chunk mappings. This gets pulled out into its own bundle so that it doesn't get pulled into either the app or vendor bundles. It could even potentially be inlined into the HTML, since it's a relatively tiny amount of script when stripped down.

1.charts.js is the lazy-loaded bundle that contains 3rd-party library code for charting (c3 and d3). This is created by us using require.ensure in our chart directives, which tells webpack that the code will be lazy loaded. It then creates a new bundle for us, and then handles the script loading when it encounters the code at runtime.

Loaders

webpack understands only JavaScript. It is the duty of loaders to transform anything else into a JavaScript module if it is to be consumed by webpack. html, markdown, css, and images are just the start of what can be loaded as a module by webpack.

html-loader, as the name implies, loads html as a module. For the project, this allows us to require our HTML templates for both routes and directives. Previously, we had used templateUrl throughout the code, which allowed us to dynamically request the templates. And during build, we would insert all of the templates into the $templateCache. Now, with webpack, we've changed over to using template, which takes a string. Fortunately, the html-loader will produce a text chunk when requiring an HTML file. So we can do:

{
  ...
  template: require('./chart.html')
  ...
}

Running Tests

The UI has unit tests that are executed through the Karma Test Runner and described using Mocha and Chai.

  • npm run test:dev will launch the Karma Test Runner, keeping it alive, and it will watch for changes to source files and execute the test suite again
  • npm run test:dev -- --single-run will execute the test suite once and then terminate
  • npm test will execute the test suite once using only PhantomJS, which is optimal for headless environments or when you want a quick test run

The Gallery

This project has several reusable components that have been created from scratch or based on existing libraries. Sometimes, an example speaks louder than documentation. To check out a collection of the available components with some useful examples, the gallery can be brought up (in development mode only) by navigating to Component Gallery.

Preferences service

The application has a localStorage-based preference service. To use it, inject preferences into your controller, service, or directive. The preferences are based on keys, which can be any string. Using some kind of namespace helps to keep everything organized (e.g., 'pi.my.app.preference').

  • preferences.get(key) - Retrieves a value from located by key in preference store. Returns undefined if it doesn't exist
  • preferences.set(key, value) - Persists the value of any type in preference store, which can be accessed through key -- preferences.remove(key) - Removes the preference identified by key from the preference store
angular.module('piDashboard').controller('DeviceCtrl', ['preferences', DeviceCtrl]);
 
function DeviceCtrl(preferences) {
    // Gets the default device - the key we've chosen is pi.default.device 
    this.defaultDevice = preferences.get('pi.default.device');
 
    this.deviceClicked = deviceClicked;
 
    this.reset = reset;
 
    function deviceClicked(device) {
        // Saves the clicked device as the default, which will 
        // be loaded the next time this controller is created 
        preferences.set('pi.default.device', device);
    }
 
    function reset() {
        // Removes the preference from localStorage 
        preferences.remove('pi.default.device');
    }
}
 

Generic modals

The application provides a couple reusable modals for interacting with users. The modal instances are provided as a factory to the application, and thus can be injected into your directives or controllers.

alertModal

alertModalInstance.then(function (instance) {
    instance.prompt({onConfirm: confirmed, message: 'You are over your daily limit.', title: 'Daily limit'});
});

confirmModal

confirmModalInstance.then(function (instance) {
    instance.prompt({onConfirm: continueOperation, message: 'Are you sure that you want to continue?', title: 'Continue'});
});

Custom Checkboxes

To provide a consistent theme across browsers, this project has a directive for for a themeable checkbox. As with the native checkbox, you may specify ng-model and ng-change attributes.

The code below will create a list of checkboxes that have a label displaying the sensor name. When the state of checkbox changes, the sensor's selected field is updated, since it is referenced by the ng-model attribute. Additionally, the controller's changed method is invoked, providing the sensor as an argument.

<ul>
    <li ng-repeat="sensor in vm.sensors">
        <pi-checkbox ng-model="sensor.selected" ng-change="vm.changed(sensor)">{{sensor.name}}</pi-checkbox>
    </li>
</ul>

Since

We've added a very lightweight service that gives a fuzzy 'time since' string for a provided date. For example, if you passed a date that occurred less than a minute ago, the service would return 'Just now'. Or if the date passed was less than two days ago, it would say 'Yesterday'.

angular.module('piDashboard').controller('LogCtrl', ['logs', 'since', LogCtrl]);
 
function LogCtrl(logs, since) {
    // Gets the time of the most recent event from the logs service and passes that to since. 
    this.eventOccurred = since(logs.getRecentEvent().getTime());
}

Spinners

The project has some CSS-based spinners that can be shown to indicate indeterminate progress. There are two classes spinner and spinner-brand, where spinner-brand applies the brand color to the spinner.

<!-- The plain spinner -->
<div class="spinner"></div>
<!-- The branded spinner -->
<div class="spinner spinner-brand"></div>

Site Management

In order to create a site, follow the following steps:

  • Navigate to the "Management" tab.
  • Click "Sites"
  • Press "Add Site" button on the top right corner.
  • Enter in Site information. Note: Name and Timezone are required fields.
  • Press "Save"

In order to edit a site, follow the following steps:

  • Pick sites that you would like to edit.
  • Click "Edit"
  • Modify the fields you would like to edit.
  • Press "Save"

In order to delete a site, follow the following steps:

  • Pick sites that you would like to edit.
  • Click "Delete"
  • Press "OK"

Geofence support

The geofence related UI is currently hidden when deployed on Bluemix. It can be made visible for some tenants. To make it visible to a tenant, you must set the following property on this tenant using the REST API: 'features.@geofences: true'