Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

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

  • Code Block
    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:

      Code Block
      languagejs
      firstline1
      titlehome.js
      collapsetrue
      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

      Code Block
      languagecss
      firstline1
      titlelayout.sass
      collapsetrue
      .image
       height: 30
       margin-left: -25px
      
      .small-image
       height: 20px


    • Your home_controller.rb should look like this:

      Code Block
      languageruby
      firstline1
      titlehome_controller.rb
      linenumberstrue
      collapsetrue
      # 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:

      Info
      titleFront End Customization

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


      Code Block
      languagexml
      firstline1
      titleindex.html.haml
      collapsetrue
      !!!
      .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:

      Code Block
      languageruby
      firstline1
      titleUpdateOrganizationMetadata
      collapsetrue
      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


      Code Block
      languageruby
      firstline1
      titleAddMetadataToIdMap
      collapsetrue
      class AddMetadataToIdMap < ActiveRecord::Migration
        def change
            add_column :id_maps, :metadata, :text
        end
      end


    • Add these to spec_helper.rb:

      Code Block
      languageruby
      firstline1
      titlespec_helper
      collapsetrue
      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:

      Code Block
      languageruby
      firstline1
      titlehome_controller_spec
      collapsetrue
      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