Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[APPINT-1044] Feature/app setup #563

Open
wants to merge 20 commits into
base: feature/app-setup
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ json.status app_instance.status
json.oauth_keys_valid app_instance.oauth_keys_valid
json.created_at app_instance.created_at
json.per_user_licence app_instance.per_user_licence
json.channel_id app_instance.channel_id

if app_instance.oauth_company
json.oauth_company_name app_instance.oauth_company
end

if app_instance.addon_organization
json.addon_organization app_instance.addon_organization
end
#
# if app_instance.connector_stack? && app_instance.oauth_keys && app_instance.oauth_keys[:version]
# json.connector_version app_instance.oauth_keys[:version]
Expand Down
12 changes: 11 additions & 1 deletion api/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,17 @@
end

# AppInstances
resources :app_instances, only: [:index, :create, :destroy], shallow: true
resources :app_instances, only: [:index, :create, :destroy], shallow: true do
member do
get :setup_form
post :create_omniauth
post :sync
get :sync_history
get :id_maps
post :disconnect
put :update_addon_synchronized_entities
end
end
# ProductInstances
resources :product_instances, only: [:index, :create, :destroy], shallow: true

Expand Down
12 changes: 12 additions & 0 deletions api/lib/mno_enterprise/add_on_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module MnoEnterprise
module AddOnHelper

def self.send_request(instance, method, path, options = {})
url = instance.metadata['app']['host'] + path
options.merge!(basic_auth: { username: instance.app.uid, password: instance.app.api_key }) if instance.app
HTTParty.public_send(method, url, options)
rescue => e
Rails.logger.info("Error on request #{url} with options #{options}: #{e}")
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module MnoEnterprise::Concerns::Controllers::Jpi::V1::AppInstancesController
# GET /mnoe/jpi/v1/organization/1/app_instances
def index
statuses = MnoEnterprise::AppInstance::ACTIVE_STATUSES.join(',')
@app_instances = MnoEnterprise::AppInstance.includes(:app).where(owner_id: parent_organization.id, 'status.in': statuses, 'fulfilled_only': true).to_a.select do |i|
@app_instances = MnoEnterprise::AppInstance.select(MnoEnterprise::AppInstance::REQUIRED_INDEX_FIELDS).includes(:app).where(owner_id: parent_organization.id, 'status.in': statuses, 'fulfilled_only': true).to_a.select do |i|
can?(:access,i)
end
end
Expand All @@ -31,14 +31,89 @@ def create

# DELETE /mnoe/jpi/v1/app_instances/1
def destroy
@app_instance = MnoEnterprise::AppInstance.find_one(params[:id])
@app_instance = MnoEnterprise::AppInstance.find_one(params[:id], :owner)
if @app_instance
organization = MnoEnterprise::Organization.find_one(@app_instance.owner_id)
authorize! :manage_app_instances, organization
authorize! :manage_app_instances, @app_instance.owner
MnoEnterprise::EventLogger.info('app_destroy', current_user.id, 'App destroyed', @app_instance)
@app_instance = @app_instance.terminate!
end

head :accepted
end

# GET /mnoe/jpi/v1/organization/1/app_instances/11/setup_form
def setup_form
app_instance = MnoEnterprise::AppInstance.find_one(params[:id], :app, :owner)
authorize! :manage_app_instances, app_instance.owner
response = MnoEnterprise::AddOnHelper.send_request(app_instance, :get, '/maestrano/api/account/setup_form')
render json: JSON.parse(response.body)
end

# POST /mnoe/jpi/v1/organization/1/app_instances/11/create_omniauth
# params[:app_instance] contains the fields values from the setup form
def create_omniauth
app_instance = MnoEnterprise::AppInstance.find_one(params[:id], :app, :owner)
authorize! :manage_app_instances, app_instance.owner
body = params[:app_instance].merge!(org_uid: app_instance.channel_id)
response = MnoEnterprise::AddOnHelper.send_request(app_instance, :post, "/maestrano/api/account/link_account", body: body)
MnoEnterprise::EventLogger.info('addon_create_omniauth', current_user.id, 'Link account to add_on', app_instance)
render json: JSON.parse(response.body)
end

# POST /mnoe/jpi/v1/organization/1/app_instances/11/sync
def sync
app_instance = MnoEnterprise::AppInstance.find_one(params[:id], :app, :owner)
authorize! :manage_app_instances, app_instance.owner
body = { group_id: app_instance.uid, opts: { full_sync: params[:full_sync] } }
response = MnoEnterprise::AddOnHelper.send_request(app_instance, :post, app_instance.metadata['app']['synchronization_start_path'], body: body)
MnoEnterprise::EventLogger.info('addon_sync', current_user.id, 'Launch sync on add_on', app_instance)
head :accepted
end

# POST /mnoe/jpi/v1/organization/1/app_instances/11/disconnect
def disconnect
app_instance = MnoEnterprise::AppInstance.find_one(params[:id], :app, :owner)
authorize! :manage_app_instances, app_instance.owner
body = { uid: app_instance.uid }
response = MnoEnterprise::AddOnHelper.send_request(app_instance, :post, '/maestrano/api/account/unlink_account', body: body)
MnoEnterprise::EventLogger.info('addon_disconnect', current_user.id, 'Unlink account from add_on', app_instance)
head :accepted
end

# GET /mnoe/jpi/v1/organization/1/app_instances/11/sync_history
# params should respect JSON Api specification
def sync_history
app_instance = MnoEnterprise::AppInstance.find_one(params[:id], :owner)
authorize! :manage_app_instances, app_instance.owner
syncs = app_instance.sync_history(params.except(:id, :organization_id, :action, :controller))
response.headers['x-total-count'] = syncs.meta[:record_count]
render json: syncs.as_json
end

# GET /mnoe/jpi/v1/organization/1/app_instances/11/id_maps
# params should respect JSON Api specification
def id_maps
app_instance = MnoEnterprise::AppInstance.find_one(params[:id], :owner)
authorize! :manage_app_instances, app_instance.owner
id_maps = app_instance.id_maps(params.except(:id, :organization_id, :action, :controller))
response.headers['x-total-count'] = id_maps.meta[:record_count]
render json: id_maps.as_json
end

# PUT /mnoe/jpi/v1/organization/1/app_instances/11/update_addon_synchronized_entities
def update_addon_synchronized_entities
app_instance = MnoEnterprise::AppInstance.find_one(params[:id], :app, :owner)
authorize! :manage_app_instances, app_instance.owner
body = {
data: {
type: 'organizations',
id: params[:org_id],
attributes: {
synchronized_entities: params[:entities]
}
}
}
MnoEnterprise::AddOnHelper.send_request(app_instance, :put, "/maestrano/api/organizations/#{params[:org_id]}", body: body.to_json, headers: {'Content-Type' => 'application/vnd.api+json'})
head :accepted
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ module MnoEnterprise

let(:app_instance) { build(:app_instance, status: 'running' , under_free_trial: false) }

before { stub_api_v2(:get, '/app_instances', [app_instance], [:app], {filter: {owner_id: organization.id, 'status.in': MnoEnterprise::AppInstance::ACTIVE_STATUSES.join(','), 'fulfilled_only': true }}) }
before { stub_api_v2(:get, '/app_instances', [app_instance], [:app], {fields: {app_instances: MnoEnterprise::AppInstance::REQUIRED_INDEX_FIELDS.join(',')}, filter: {owner_id: organization.id, 'status.in': MnoEnterprise::AppInstance::ACTIVE_STATUSES.join(','), 'fulfilled_only': true }}) }

before { sign_in user }
subject { get :index, organization_id: organization.id }
Expand All @@ -40,7 +40,7 @@ module MnoEnterprise
subject
# TODO: Test that the rendered json is the expected one
# expect(assigns(:app_instances)).to eq([app_instance])
assert_requested(:get, api_v2_url('/app_instances', [:app], {_locale: I18n.locale, filter: {owner_id: organization.id, 'status.in': MnoEnterprise::AppInstance::ACTIVE_STATUSES.join(','), 'fulfilled_only': true }}))
assert_requested(:get, api_v2_url('/app_instances', [:app], {fields: {app_instances: MnoEnterprise::AppInstance::REQUIRED_INDEX_FIELDS.join(',')}, _locale: I18n.locale, filter: {owner_id: organization.id, 'status.in': MnoEnterprise::AppInstance::ACTIVE_STATUSES.join(','), 'fulfilled_only': true }}))
}
end

Expand Down Expand Up @@ -74,9 +74,8 @@ module MnoEnterprise
before { stub_audit_events }
let(:app_instance) { build(:app_instance) }
let(:terminated_app_instance) { build(:app_instance, id: app_instance.id, status: 'terminated') }
before { stub_api_v2(:get, "/app_instances/#{app_instance.id}", app_instance)}
before { stub_api_v2(:delete, "/app_instances/#{app_instance.id}/terminate", terminated_app_instance)}
before { stub_api_v2(:get, "/organizations/#{app_instance.owner_id}")}
before { stub_api_v2(:get, "/app_instances/#{app_instance.id}", app_instance, [:owner]) }
before { stub_api_v2(:delete, "/app_instances/#{app_instance.id}/terminate", terminated_app_instance) }
before { sign_in user }
subject { delete :destroy, id: app_instance.id }

Expand All @@ -88,5 +87,137 @@ module MnoEnterprise
expect(assigns(:app_instance).status).to eq('terminated')
}
end

describe 'GET #setup_form' do
before { stub_audit_events }
let(:app_instance) { build(:app_instance, metadata: { app: { host: 'http://www.addon-url.com'} }) }
let(:form) { { form: {} } }
before { stub_api_v2(:get, "/app_instances/#{app_instance.id}", app_instance, [:app, :owner])}
before { stub_add_on(app_instance, :get, '/maestrano/api/account/setup_form', 200, form) }
before { sign_in user }
subject { get :setup_form, id: app_instance.id }

it_behaves_like 'jpi v1 protected action'

it {
subject
expect(JSON.parse(response.body)).to eq(form.with_indifferent_access)
}
end

describe 'POST #create_omniauth' do
before { stub_audit_events }
let(:app_instance) { build(:app_instance, metadata: { app: { host: 'http://www.addon-url.com' } }) }
before { stub_api_v2(:get, "/app_instances/#{app_instance.id}", app_instance, [:app, :owner])}
before { stub_add_on(app_instance, :post, "/maestrano/api/account/link_account", 202) }
before { sign_in user }
subject { post :create_omniauth, id: app_instance.id, app_instance: {} }

it_behaves_like 'jpi v1 protected action'

it {
subject
expect(subject).to be_successful
}
end

describe 'POST #sync' do
before { stub_audit_events }
let(:app_instance) { build(:app_instance, metadata: { app: { host: 'http://www.addon-url.com', synchronization_start_path: '/sync' } }) }
before { stub_api_v2(:get, "/app_instances/#{app_instance.id}", app_instance, [:app, :owner])}
before { stub_add_on(app_instance, :post, '/sync', 202) }
before { sign_in user }
subject { post :sync, id: app_instance.id }

it_behaves_like 'jpi v1 protected action'

it {
subject
expect(subject).to be_successful
}
end

describe 'POST #disconnect' do
before { stub_audit_events }
let(:app_instance) { build(:app_instance, metadata: { app: { host: 'http://www.addon-url.com' } }) }
before { stub_api_v2(:get, "/app_instances/#{app_instance.id}", app_instance, [:app, :owner])}
before { stub_add_on(app_instance, :post, '/maestrano/api/account/unlink_account', 202) }
before { sign_in user }
subject { post :disconnect, id: app_instance.id }

it_behaves_like 'jpi v1 protected action'

it {
subject
expect(subject).to be_successful
}
end

describe 'GET #sync_history' do
before { stub_audit_events }
let(:app_instance) { build(:app_instance) }
before { stub_api_v2(:get, "/app_instances/#{app_instance.id}", app_instance, [:owner])}
let(:sync) {
{
status: "SUCCESS",
message: nil,
updated_at: "2017-10-03T23:16:25Z",
created_at:"2017-10-03T23:16:08Z"
}
}
before { stub_api_v2(:get, "/app_instances/#{app_instance.id}/sync_history", sync) }
before { sign_in user }
subject { get :sync_history, id: app_instance.id }

it_behaves_like 'jpi v1 protected action'

it {
subject
assert_requested_api_v2(:get, "/app_instances/#{app_instance.id}/sync_history")
expect(JSON.parse(response.body).first['attributes']).to eq(sync.with_indifferent_access)
}
end

describe 'GET #id_maps' do
before { stub_audit_events }
let(:app_instance) { build(:app_instance) }
before { stub_api_v2(:get, "/app_instances/#{app_instance.id}", app_instance, [:owner]) }
let(:id_map) {
{
connec_id: "8d8781c0-94b7-0135-43e8-245e60e5955b",
external_entity: "product",
external_id: '1',
name: "Product1",
message: "An error ocurred"
}
}
before { stub_api_v2(:get, "/app_instances/#{app_instance.id}/id_maps", id_map) }
before { sign_in user }
subject { get :id_maps, id: app_instance.id }

it_behaves_like 'jpi v1 protected action'

it {
subject
assert_requested_api_v2(:get, "/app_instances/#{app_instance.id}/id_maps")
expect(JSON.parse(response.body).first['attributes']).to eq(id_map.with_indifferent_access)
}
end

describe 'PUT #update_addon_synchronized_entities' do
let(:app_instance) { build(:app_instance, metadata: { app: { host: 'http://www.addon-url.com' } }) }
let(:org_id) { 1 }
before { stub_api_v2(:get, "/app_instances/#{app_instance.id}", app_instance, [:app, :owner]) }
before { stub_add_on(app_instance, :put, "/maestrano/api/organizations/#{org_id}", 202) }
before { sign_in user }
subject { put :update_addon_synchronized_entities, id: app_instance.id, org_id: org_id }

it_behaves_like 'jpi v1 protected action'

it {
subject
expect(subject).to be_successful
}
end
end
end
23 changes: 23 additions & 0 deletions core/config/locales/templates/components/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,26 @@ en:
change_language: 'Change language'
confirm_header: 'Change language to {name}?'
confirm: 'Are you sure you want to change language to {name}?'

addon_connect:
title: "Connect to {appname}"
next: Next
back: Back
link_account:
title: Link your account
link: "Link your {appname} account to Maestrano to get your business in sync."
submit_form: Submit
entities:
update_entities: Update
select: Select your entities
entity: Entity
to_app: "From Connec™ to {appname}"
chose_entities: Chose which entities you want to synchronize
to_connec: "From {appname} to Connec™"
sync:
sync_launched: "Congratulations, your data is now being synced!"
start_sync: Start synchronizing
chose_historical: Chose wether to synchronize historical data
warning_hisorical_html: "<p><b>All data</b> created prior to the date you linked {appname} <b>will be synchronized both ways</b></p><p>It means that:<br/>- all data from applications you already have linked to the platform will be sent to your {appname} account<br/>- all exisiting data from {appname} will be sent to your other applications<br/></p><p>If you have been manually copying records in multiple applications, <b>you risk seeing duplicates arising!</b></p><p><b>This action cannot be undone at any time!</b></p><b>Synchronize my historical data:</b>"
historical_unchecked: "Only data created after {date} will be synchronized"
historical_checked: "Synchronizing your historical data will share all data in {appname}. This action is not reversible. Want to know more? Check <a href=\"https://maestrano.atlassian.net/wiki/display/UKB/How+Connec%21+manages+Historical+Data+Sharing\" target=\"_blank\">here</a>"
25 changes: 24 additions & 1 deletion core/config/locales/templates/impac/dock/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,27 @@ en:
updating: Updating
upgrading: Upgrading
downgrading: Downgrading

sync_started: "The sync with {appname} has started"
sync_list:
title: "List of previous synchronizations:"
history: "History"
date: "Date"
status: "Status"
message: "Message"
sync: "Synchronize"
entities:
title: "Entities"
update: "Update"
id_maps:
title: "Data"
name: "Name"
type: "Type"
status: "Status"
disconnect_app: Disconnect
disconnect:
modal:
cancel: "Cancel"
action: "Disconnect app"
header: "Disconnect {appname}?"
body: "Are you sure you want to disconnect {appname} and Maestrano?"
success: "Your application has been disconnected"
3 changes: 3 additions & 0 deletions core/lib/mno_enterprise/concerns/models/app_instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ module MnoEnterprise::Concerns::Models::AppInstance
# delete <api_root>/app_instances/:id/terminate
custom_endpoint :terminate, on: :member, request_method: :delete
custom_endpoint :provision, on: :collection, request_method: :post
custom_endpoint :sync_history, on: :member, request_method: :get
custom_endpoint :id_maps, on: :member, request_method: :get

#==============================================================
# Constants
#==============================================================
ACTIVE_STATUSES = [:running, :stopped, :staged, :provisioning, :starting, :stopping, :updating]
TERMINATION_STATUSES = [:terminating, :terminated]
REQUIRED_INDEX_FIELDS = [:uid, :stack, :name, :status, :oauth_keys_valid, :created_at, :per_user_licence, :addon_organization, :channel_id, :oauth_company, :app, :owner_id]
end

#==================================================================
Expand Down
2 changes: 1 addition & 1 deletion core/lib/mno_enterprise/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ def self.configure_api
connection.use Faraday::Response::Logger

# Instrumentation (see below for the subscription)
connection.use FaradayMiddleware::Instrumentation if Rails.env.development?
connection.use FaradayMiddleware::Instrumentation
end
end
end
Expand Down
Loading