The Application Installer

Most of the apps will require some level of configuration from the Deskpro administrators. For this purpose, the application manifest contains a settings property which is a list of setting names and their associated form field types.

But this requires maintaining some sort of UI to interact with them which would only distract you from developing your actual applications, so we provide an out-of-the-box installer for this purpose.

The installer is built as a regular app and it is available here: https://github.com/deskpro/apps-installer .

How does it work?

During the compile and package phases, the build tool (dpat) bundles its version of apps-installer installer in the same package with your app. If you are curious, you can inspect the contents of the dist folder of your application (provided you use the apps-boilerplate for your app) and check the installer files in the dist/html and dist/assets folder.

The end result is that in the distribution bundle, your application manifest will contain a new target named install which loads the file html/install.html from your application bundle into an iframe.

The install target is only available in the Administration section when you install or update an application. The installer application from your application bundle is loaded in an iframe, then reads the settings property from your application manifest to determine if it should display a settings form and renders it. Once the form is submitted the installer reads the rest of configuration settings and proceeds with the actual installation.

Declaring settings

You declare settings using the settings property of the application manifest:

{
  "deskpro" : {
    "settings": [
      {
        "name": "clientId",
        "defaultValue": "",
        "title": "The client id",
        "required": true,
        "type": "text"
      },    
      {
        "name": "clientDescription",
        "defaultValue": "",
        "title": "A brief description of your client",
        "required": true,
        "type": "textarea"
      }      
    ]
  }
}

Each setting can have the following properties:

name

  • Required

  • Type: string

The name of your setting. Must be a valid object key

defaultValue

  • Optional

  • Type: string|boolean

title

  • Required

  • Type: string

The label displayed in the settings form for this item

required

  • Optional

  • Default value: false

  • Type: boolean

If this is set to true then the setting can not be empty and the field will be marked required in the settings form

type

  • Required

  • Type: string

  • Value: "text" | "textarea" | "boolean" | "choice"

The type of form field that should be displayed for this setting

items

  • Required only when type is set to choice

  • Type: array

The list of choices for this setting. Each item in the list is an object with the following properties:

  • title: a string, the title of this item

  • value: a string, the setting value of this item

Example:

{
  "deskpro" : {
    "settings": [
      {
        "name": "authStrategy",
        "defaultValue": "oauth2",
        "title": "Authentication method",
        "type": "choice",
        "items": [
           {
              "title": "Oauth2 Authentication",
              "value": "oauth2"            
            }, 
            {
              "title": "API Key",
              "value": "apikey"
            }            
        ]
      }      
    ]
  }
}

Customising the installer

There are cases where you applying simple validation to your settings is not enough. For instance, if you are requiring OAuth configuration and you want to make sure the credentials actually work.

For these cases, we have provided a way to create a custom settings form component, which allows you to intercept the settings collection step in the install process and add your custom logic to it. You can also add your own UI elements, or links to help pages.

The first thing you need to do is declare a dev-dependency to the the latest version of apps-boilerplate from the github branch in your package.json :

{
  "devDependencies": {
    "@deskpro/apps-installer": "github:deskpro/apps-installer#v0.4.5"
  }
}

At the time of writing this article, the latest version is 0.4.5. This tells the build tool to look for your custom settings form component and use it to build the a custom installer.

The next thing is create a module at src/installer/index.js. This module should have as a default export your custom settings form component.

Your component will act as a container for the actual settings form component, which you will have to show in your component's render method. Your component will receive as props a reference to the settings form constructor and a callback that should be invoked when you have finished processing the settings values to allow the installer to continue.

Here's how a custom settings form component that is functionally equivalent to the default one might look like:

import React from 'react';
import PropTypes from 'prop-types';

class ScreenSettings extends React.Component {

  static propTypes = {
    /**
     * @type function a callback to invoke to finish the installation
     */
    finishInstall: PropTypes.func.isRequired,

    /**
     * @type string they type of installation: install or update
     */
    installType: PropTypes.string.isRequired,

    /**
     * @type {Array<Object>} the list of settings defined in the application manifest
     */
    settings: PropTypes.array.isRequired,

    /**
     * @type {Object} a map of setting name and value
     */
    values: PropTypes.object.isRequired,

    /**
     * @type {function} the settings form constructor
     */
    settingsForm: PropTypes.func.isRequired,

    /**
     * @type {AppClient} the Deskpro Application Client
     * @see https://deskpro.github.io/apps-sdk-core/reference/AppClient.html
     */
    dpapp: PropTypes.object.isRequired
  };

  constructor() {
    super();
    // initialize our state
    this.state = { values: null, error: null };
  }

  /**
  * Submit hook, add your custom logic for values here
  *
  * @param {Object} values
  */
  onBeforeSubmit(values) { return Promise.resolve(values); }

  /**
   * Callback, invoked when the settings form is submitted
   *
   * @param {Object} values
   */
  onSubmit = (values) => {
    const { finishInstall } = this.props;

    this.onBeforeSubmit(values)
      .then(values => finishInstall(values).then(({ onStatus }) => onStatus())) // finish install
      .catch(error => this.setState({ error })) // display an error message
    ;
  };

  /**
   * Displays the settings screen
   *
   * @returns {XML}
   */
  render() {

    const { settings, settingsForm: SettingsForm } = this.props;
    const values = this.state.values || this.props.values;

    let formRef;
    return (
      <div>
        <SettingsForm settings={ settings } values={ values } onSubmit={this.onSubmit} ref={ref => formRef = ref} />
        <button className={'btn-action'} onClick={() => formRef.submit()}>Update Settings</button>

        { this.renderError() }

      </div>
    );
  }

  /**
   * Renders an error message
   *
   * @returns {XML|null}
   */
  renderError()
  {
    if (this.state.error) {
      return (<div style={{backgroundColor: "red", color:"white", padding: "2%"}}>{ this.state.error }</div>);
    }

    return null;
  }
}

export default ScreenSettings;

As you can see, your component will receive a list of properties:

installType

This a string with only two value: install and update. You should use this value if you need to treat some setting differently if the admin is performing an update, for instance when the admin wants only to change some settings.

settings

This is a list of settings as defined in your application manifest

values

This is a map of setting name - setting value, containing the initial values.

settingsForm

This is a reference to the constructor for the component which actually renders the form and fields from settings and values. In your render method you should pass it the settings, values and a submit method as props:

<SettingsForm settings={ settings } values={ values } onSubmit={this.onSubmit}/>

You should attach a ref property like in the example above if you want to be able to submit the form.

finishInstall

An async callback to be invoked when you are done processing the values. After the values are saved, the callback returns with a promise which resolves with a final callback that returns the control back to the installer when invoked.

This is how the you should use this callback:

    /**
    * @param {Object} values
    */
    onFinish = (values) => {
      const { finishInstall } = this.props;
      finishInstall(values)
        .then( installer => installer.onStatus()) // on success return control to installer
        .catch(error => this.setState({ error })) // display an error message
    };

dpapp

This is an instance of AppClient and it is injected into every app.

You can use it as you would use it in your application, for instance to make calls to the Deskpro REST API. The only thing to remember is that the authenticated user has admin privileges instead of agent privileges and your API calls will be made using admin privileges.

Last updated