Migration Guides

Step to step guide when migrating to a different version of the Maestrano Connector Framework

Migrating to version 2.1


Step-by-step guide


    • In addition to the framework version, add the following gems to your Gemfile:
      webmock
  • group :test do
      gem 'webmock'
    end
    • Download these 2 png files (used in the front-end) and add them to app/assets/images/logos
      to_external.png
      - to_connec.png
    • Replace your app/assets/javascripts/home.js:

      home.js
      function historicalDataDisplay()
      {
          if (document.getElementById('historical-data').checked)
          {
              $('#myModal').modal('show');
              document.getElementById('historical-data-display-checked').style.display = 'block';
              document.getElementById('historical-data-display-unchecked').style.display = 'none';
          } else {
              document.getElementById('historical-data-display-unchecked').style.display = 'block';
              document.getElementById('historical-data-display-checked').style.display = 'none';
          }
      }
      
      var checkHistorical
      
      function closeModal(sender)
      {
        checkHistorical = sender.id == 'confirm'
        $('#myModal').modal('hide');
      }
      
      $(function () {
        $('[data-toggle="tooltip"]').tooltip()
      })
      
      $(document).ready(function(){
        $("#myModal").on('hidden.bs.modal', function (e) {
          if (!checkHistorical) {
            document.getElementById('historical-data').checked = false
            historicalDataDisplay()
          }
          checkHistorical = false
        });
      });
    • Append these lines to app/assets/stylesheets/layout.sass

      layout.sass
      .image
       height: 30
       margin-left: -25px
      
      .small-image
       height: 20px
    • Your home_controller.rb should look like this:

      home_controller.rb
      # frozen_string_literal: true
      class HomeController < ApplicationController
        def update
          return redirect_to(:back) unless is_admin
      
          # Update list of entities to synchronize
          current_organization.synchronized_entities.keys.each do |entity|
            current_organization.synchronized_entities[entity][:can_push_to_connec] = params[entity.to_s]["to_connec"] == "1"
            current_organization.synchronized_entities[entity][:can_push_to_external] = params[entity.to_s]["to_external"] == "1"
          end
      
          full_sync = params['historical-data'].present? && !current_organization.historical_data
          opts = {full_sync: full_sync}
          current_organization.sync_enabled = current_organization.synchronized_entities.values.any? { |settings| settings.values.any? { |v| v } }
          current_organization.enable_historical_data(params['historical-data'].present?)
          trigger_sync = current_organization.sync_enabled
          current_organization.save
      
          # Trigger sync only if the sync has been enabled
          start_synchronization(opts) if trigger_sync
      
          redirect_to(:back)
        end
      
        def synchronize
          return redirect_to(:back) unless is_admin
          Maestrano::Connector::Rails::SynchronizationJob.perform_later(current_organization.id, (params['opts'] || {}).merge(forced: true))
          flash[:info] = 'Synchronization requested'
          redirect_to(:back)
        end
      
        def redirect_to_external
          redirect_to 'https://your_application.com'
        end
      
        private
      
          def start_synchronization(opts)
            Maestrano::Connector::Rails::SynchronizationJob.perform_later(current_organization.id, opts)
            flash[:info] = 'Congrats, you\'re all set up! Your data are now being synced' if current_organization.sync_enabled_changed?
          end
      end
    • Refer to this html.haml file for the modal and layout:

      Front End Customization

      Based on the specific requirements of the connector, this file might have to be changed. (i.e. Authentication form)

      index.html.haml
      !!!
      .home
        .banners
          .row
            .col-md-10.col-md-offset-2
              %h2 YourApp Connector
              %p
                -if current_organization
                  Link your company <strong>#{current_organization.name} (#{current_organization.uid})</strong> to YourApp to get your business in synch. Check the status of your connection on this screen.
                -else
                  Link your account to YourApp to get your business in synch. Check the status of your connection on this screen.
      
        .container
          - if current_user
            - unless is_admin
              .row
                .col-md-12.alert.alert-warning
                  Only administrators can modify the application settings
      
            .row.link-step{class: "#{current_organization.oauth_uid ? 'done' : 'todo'}"}
              .col-md-1.text-center.link-step-number
                %span.badge.link-step-badge
                  1
              .col-md-6.link-step-description
                %h
                  - if current_organization.oauth_uid
                    Your YourApp account <strong>#{current_organization.oauth_name} (#{current_organization.oauth_uid})</strong> is currently linked
                  - else
                    Your YourApp account is not linked
                    %br
              .col-md-4.col-md-offset-8.text-center.link-step-action
                - if current_organization.oauth_uid
                  = link_to "Disconnect", signout_omniauth_path(organization_id: current_organization.id), class: "btn btn-warning btn-lg #{is_admin ? '' : 'disabled'}"
                - else
                  - if is_admin
                    .col-md-12.col-md-offset-1.text-center
                    %small Your YourApp email and password are not stored by Maestrano
                    = render 'authentication_form'
                    %br
                    %small If you don’t have an account #{link_to 'create yours here', Maestrano::Connector::Rails::External.create_account_link(current_organization || nil)}
      
            .spacer1
      
            .row.link-step{class: "#{(current_organization.sync_enabled && current_organization.synchronized_entities.values.any?) ? 'done' : 'todo'}"}
              = form_tag home_update_path(id: current_organization.id), method: :put do
                .col-md-1.text-center.link-step-number
                  %span.badge.link-step-badge 2
                .col-md-9.link-step-description
                  %p You can customize which entities are synchronized by the connector:
                  %p (#{image_tag "logos/to_connec.png", class: "small-image"} : from YourApp to Connec! and #{image_tag "logos/to_external.png", class: "small-image"} : from Connec! to YourApp)
                  .spacer1
                  .row
                    .col-md-11.col-md-offset-1.center
                      .row
                        .col-md-1
                          =image_tag "logos/to_connec.png", class: "image"
                        .col-md-1
                          =image_tag "logos/to_external.png", class: "image"
                        .col-md-4
                          YourApp wording
                        .col-md-3
                          Universal wording
                  .spacer1
                  .row
                    .col-md-11.col-md-offset-1
                      - current_organization.displayable_synchronized_entities.each do |k, v|
                        .row.sync-entity
                          .col-md-1.link-step-action
                            #{check_box("#{k}", "to_connec", {checked: (v[:can_push_to_connec] || v[:can_push_to_external]) && !current_organization.pull_disabled, onclick: "return !#{k}_to_external.checked;", disabled: current_organization.pull_disabled})}
                          .col-md-1.link-step-action
                            #{check_box("#{k}", "to_external", {checked: v[:can_push_to_external] && !current_organization.push_disabled, onchange: "#{k}_to_connec.checked = #{!current_organization.pull_disabled};", disabled: current_organization.push_disabled})}
                          %label.col-md-8{:for => "#{k}", style: 'padding-top: 5px;'}
                            .col-md-6
                              #{v[:external_name]}
                            .col-md-6
                              #{v[:connec_name]}
                          -if is_admin
                            .col-md-2.text-right
                              - if v && current_organization.oauth_uid && current_organization.sync_enabled
                                = link_to 'Force a synchronization', home_synchronize_path(opts: {only_entities: [k.to_s]}), method: :post, class: 'btn btn-warning btn-sm', title: "Force a synchronization for #{v[:external_name]} only", 'data-toggle' => 'tooltip', 'data-placement' => 'right'
      
                  .spacer2
                  .row
                    %h Chose whether to synchronize your historical data:
                  .spacer1
                  .row
                    .col-md-4.col-md-offset-1
                      %label{:for => 'historical-data'} Synchronize my historical data
                    .col-md-1
                      #myModal.modal.fade{:role => "dialog"}
                        .modal-dialog
                          .modal-content
                            .modal-header
                              %button.close{"data-dismiss" => "modal", :type => "button"} ×
                              %h4.modal-title Warning!
                            .modal-body
                              %p
                                %b All data
                                created prior to the date you linked YourApp
                                %b will be synchronised both ways.
                              %p
                                It means that:
                                %br
                                \- all data from applications you already have linked to the platform will be sent to your YourApp account
                                %br
                                \- all exisiting data from YourApp will be sent to your other applications
                                %br
                              %p
                                If you have been manually copying records in multiple applications,
                                %b you risk seeing duplicates arising!
                              %p
                                %b This action cannot be undone at any time!
                            .modal-footer
                              %button.btn.btn-primary{id: 'confirm', :type => "button", onclick: "closeModal(confirm);"}  Confirm
                              %button.btn.btn-secondary{id: 'close', :type => "button", onclick: "closeModal(close);"} Close
                      %input{type: 'checkbox', id: 'historical-data', name: 'historical-data', checked: current_organization.historical_data, onchange: 'historicalDataDisplay();', disabled: current_organization.historical_data}
                    .col-md-6
                      %small#historical-data-display-unchecked{style: "display: #{current_organization.historical_data ? 'none' : 'block'}"} Only data created after #{(current_organization.date_filtering_limit && current_organization.date_filtering_limit.utc || Time.now.utc).to_formatted_s(:long_ordinal)} will be synchronized
                      %small#historical-data-display-checked{style: "display: #{!current_organization.historical_data ? 'none' : 'block'}"}
                        Synchronizing your historical data will share all data in YourApp. This action is not reversible. Want to know more? Check #{link_to 'here', 'https://maestrano.atlassian.net/wiki/display/UKB/How+Connec%21+manages+Historical+Data+Sharing'}
      
                .spacer1
                .row
                  .col-md-2.col-md-offset-10.text-center.link-step-action
                    =submit_tag "#{current_organization.sync_enabled ? 'Synchronize' : 'Start synchronizing!'}", class: "btn btn-lg btn-warning #{current_organization.oauth_uid ? '' : 'disabled'} text-sm"
      
            -if current_organization.oauth_uid && current_organization.sync_enabled
              .spacer2
              .row
                .col-md-4.col-md-offset-4.text-center
                  = link_to 'Go to YourApp', home_redirect_to_external_path, class: 'btn btn-lg btn-primary'
      
          - else
            .row
              .col-md-4.col-md-offset-4.center
                = link_to 'Link your Maestrano account', Maestrano::Connector::Rails::Engine.routes.url_helpers.default_maestrano_auth_saml_index_path(tenant: :default), class: 'btn btn-warning'
    • Add these 2 migrations:

      UpdateOrganizationMetadata
      class UpdateOrganizationMetadata < ActiveRecord::Migration
        def change
            add_column :organizations, :push_disabled, :boolean
            add_column :organizations, :pull_disabled, :boolean
          change_column :organizations, :synchronized_entities, :text
      
            # Migration to update the way we handle synchronized_entities for data sharing.
            # Before : synchronized_entities = {company: true}
            # After: synchronized_entities = {company: {can_push_to_connec: true, can_push_to_external: true}}
      
            #We also add metadata from MnoHub
          Maestrano::Connector::Rails::Organization.all.each do |o|
            o.reset_synchronized_entities
            o.enable_historical_data(true) if o.push_disabled
          end
        end
      end
      AddMetadataToIdMap
      class AddMetadataToIdMap < ActiveRecord::Migration
        def change
            add_column :id_maps, :metadata, :text
        end
      end
    • Add these to spec_helper.rb:

      spec_helper
      require 'webmock/rspec'
      
      ...
      config.before(:each) do
       stub_request(:get, %r(https://maestrano.com/api/v1/account/groups/\w+))
       .with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'Basic Og==', 'User-Agent'=>'Ruby'})
       .to_return({status: 200, body: "{}", headers: {}})
      end
    • Refer to this file to fix home_controller_spec:

      home_controller_spec
      require 'spec_helper'
      
      describe HomeController, type: :controller do
       let(:back_path) { home_index_path }
       before(:each) do
       request.env["HTTP_REFERER"] = back_path
       end
      
       describe 'index' do
       subject { get :index }
      
       it { expect(subject).to be_success }
       end
      
       describe 'update' do
       let(:user) { create(:user) }
       let(:organization) { create(:organization, synchronized_entities: {'people' => {can_push_to_connec: false, can_push_to_external: false}, 'item' => {can_push_to_connec: true, can_push_to_external: true}}) }
      
       before { allow_any_instance_of(Maestrano::Connector::Rails::SessionHelper).to receive(:current_user).and_return(user) }
       before { allow_any_instance_of(Maestrano::Connector::Rails::SessionHelper).to receive(:current_organization).and_return(organization) }
      
       subject { put :update, id: organization.id, 'people' => {to_connec: '1', to_external: '1'}, 'item' => {}, 'lala' => {} }
      
       context 'when user is not admin' do
       before { allow_any_instance_of(Maestrano::Connector::Rails::SessionHelper).to receive(:is_admin).and_return(false) }
      
       it { expect(subject).to redirect_to back_path }
       end
      
       context 'when user is admin' do
       before { allow_any_instance_of(Maestrano::Connector::Rails::SessionHelper).to receive(:is_admin).and_return(true) }
      
       it { expect(subject).to redirect_to back_path }
      
       it 'updates organization synchronized_entities' do
       subject
       organization.reload
       expect(organization.synchronized_entities).to eq('people' => {can_push_to_connec: true, can_push_to_external: true}, 'item' => {can_push_to_connec: false, can_push_to_external: false})
       end
      
       it 'updates organization sync_enabled' do
       subject
       organization.reload
       expect(organization.sync_enabled).to eq true
       end
      
       context 'when removing all entities' do
       subject { put :update, id: organization.id, 'people' => {}, 'item' => {} }
       before { organization.update(sync_enabled: true) }
      
       it 'set sync_enabled to false' do
       subject
       organization.reload
       expect(organization.sync_enabled).to eq false
       end
       end
       end
       end
      
       describe 'synchronize' do
       let(:user) { create(:user) }
       let(:organization) { create(:organization, synchronized_entities: {'people' => {can_push_to_connec: false, can_push_to_external: false}, 'item' => {can_push_to_connec: true, can_push_to_external: true}}) }
      
       before { allow_any_instance_of(Maestrano::Connector::Rails::SessionHelper).to receive(:current_user).and_return(user) }
       before { allow_any_instance_of(Maestrano::Connector::Rails::SessionHelper).to receive(:current_organization).and_return(organization) }
      
       subject { post :synchronize }
      
       context 'when user is not admin' do
       before { allow_any_instance_of(Maestrano::Connector::Rails::SessionHelper).to receive(:is_admin).and_return(false) }
      
       it { expect(subject).to redirect_to back_path }
      
       it 'does nothing' do
       expect(Maestrano::Connector::Rails::SynchronizationJob).to_not receive(:perform_later)
       subject
       end
       end
      
       context 'when user is admin' do
       before { allow_any_instance_of(Maestrano::Connector::Rails::SessionHelper).to receive(:is_admin).and_return(true) }
      
       it { expect(subject).to redirect_to back_path }
      
       context 'with opts' do
       subject { post :synchronize, opts: {'opts' => 'some_opts'} }
      
       it 'calls perform_later with opts' do
       expect(Maestrano::Connector::Rails::SynchronizationJob).to receive(:perform_later).with(organization.id, 'opts' => 'some_opts', forced: true)
       subject
       end
       end
      
       context 'without opts' do
       subject { post :synchronize}
      
       it 'calls perform_later with empty opts hash' do
       expect(Maestrano::Connector::Rails::SynchronizationJob).to receive(:perform_later).with(organization.id, forced: true)
       subject
       end
       end
       end
       end
      
      
       describe 'redirect_to_external' do
       subject { get :redirect_to_external }
      
       context 'otherwise' do
       it {expect(subject).to redirect_to('https://your_application.com')}
       end
       end
      end