Skip to content
Draft
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
88 changes: 88 additions & 0 deletions app/controllers/api/v3/saved_scenario_users_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# frozen_string_literal: true

module Api
module V3
class SavedScenarioUsersController < BaseController
before_action do
authorize!(:update, Scenario)
end

def index
response = my_etm_client.get(
"/api/v1/saved_scenarios/#{params[:saved_scenario_id]}/users"
)

render json: response.body
rescue Faraday::ResourceNotFound
render_not_found
rescue Faraday::Error => e
handle_faraday_error(e)
end

def create
response = my_etm_client.post(
"/api/v1/saved_scenarios/#{params[:saved_scenario_id]}/users",
users_payload
)

render json: response.body, status: :created
rescue Faraday::ResourceNotFound
render_not_found
rescue Faraday::Error => e
handle_faraday_error(e)
end

def update
response = my_etm_client.put(
"/api/v1/saved_scenarios/#{params[:saved_scenario_id]}/users",
users_payload
)

render json: response.body
rescue Faraday::ResourceNotFound
render_not_found
rescue Faraday::Error => e
handle_faraday_error(e)
end

def destroy
response = my_etm_client.delete(
"/api/v1/saved_scenarios/#{params[:saved_scenario_id]}/users"
) do |req|
req.headers['Content-Type'] = 'application/json'
req.body = users_payload.to_json
end

render json: response.body
rescue Faraday::ResourceNotFound
render_not_found
rescue Faraday::Error => e
handle_faraday_error(e)
end

private

def users_payload
users = params.permit(
saved_scenario_users: %i[id role user_id user_email]
)[:saved_scenario_users]

{
saved_scenario_users: users&.map(&:to_h) || []
}
end

def handle_faraday_error(error)
if error.response
status = error.response[:status]
body = error.response[:body]

render json: body, status:
else
render json: { errors: ['Failed to connect to MyETM'] },
status: :service_unavailable
end
end
end
end
end
8 changes: 8 additions & 0 deletions app/controllers/api/v3/scenario_users_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module Api
module V3
# NOTE: a lot of logic in this controller should not be here. One day this
Expand Down Expand Up @@ -63,6 +65,12 @@ def destroy
json_response
end

# DELETE /api/v3/scenarios/:scenario_id/users
def destroy_all
@scenario.scenario_users.destroy_all
head :no_content
end

private

def scenario_user_params
Expand Down
79 changes: 53 additions & 26 deletions app/models/scenario.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Scenario < ApplicationRecord

belongs_to :parent, class_name: 'Scenario', foreign_key: :preset_scenario_id, optional: true

has_one :preset_scenario, :foreign_key => 'preset_scenario_id', :class_name => 'Scenario'
has_one :preset_scenario, foreign_key: 'preset_scenario_id', class_name: 'Scenario'
has_one :scaler, class_name: 'ScenarioScaling', dependent: :delete
has_one :scenario_version_tag, dependent: :destroy
has_many :heat_network_orders, dependent: :destroy
Expand All @@ -35,7 +35,6 @@ class Scenario < ApplicationRecord
has_many :attachments, dependent: :destroy, class_name: 'ScenarioAttachment'
has_many :user_curves, dependent: :destroy


has_many :source_attachments,
dependent: :nullify,
class_name: 'ScenarioAttachment',
Expand All @@ -62,11 +61,11 @@ class Scenario < ApplicationRecord

validates_associated :scaler, on: :create

scope :by_id, ->(q) { where(id: q)}
scope :by_id, ->(q) { where(id: q) }

# Expired ApiScenario will be deleted by rake task :clean_expired_api_scenarios
scope :expired, -> { where(['updated_at < ?', Date.today - 14]) }
scope :recent, -> { order("created_at DESC").limit(30) }
scope :recent, -> { order('created_at DESC').limit(30) }
scope :recent_first, -> { order('created_at DESC') }

scope :with_attachments, -> { includes(attachments: { file_attachment: :blob }) }
Expand All @@ -88,14 +87,12 @@ class Scenario < ApplicationRecord
attr_accessor :input_errors, :ordering, :display_group, :descale

before_create do |scenario|
if preset = scenario.preset_scenario
if (preset = scenario.preset_scenario)
scenario.copy_scenario_state(preset)
end
end

def test_scenario=(flag)
@test_scenario = flag
end
attr_writer :test_scenario

def test_scenario?
@test_scenario == true
Expand All @@ -107,9 +104,9 @@ def self.default(opts = {})

def self.default_attributes
{
:area_code => Etsource::Config.default_dataset_key,
:user_values => {},
:end_year => 2050
area_code: Etsource::Config.default_dataset_key,
user_values: {},
end_year: 2050
}.with_indifferent_access
end

Expand All @@ -119,7 +116,7 @@ def self.new_attributes(settings = {})
out = attributes.merge(settings)
# strip invalid attributes
valid_attributes = [column_names, 'scenario_id'].flatten
out.delete_if{|key,v| !valid_attributes.include?(key.to_s)}
out.delete_if { |key, _v| valid_attributes.exclude?(key.to_s) }
out
end

Expand All @@ -133,14 +130,14 @@ def self.find_for_calculation(id)
id_attr = type_for_attribute(:id)
id = id_attr.cast(id)

if id.to_i >= 1 << (id_attr.limit * 8 - 1)
if id.to_i >= 1 << ((id_attr.limit * 8) - 1)
raise(
ActiveRecord::RecordNotFound,
"Couldn't find Scenario with an out of range value for 'id'"
)
end

where(id: id).with_attachments.first!
where(id:).with_attachments.first!
end

def self.owned_by?(user)
Expand Down Expand Up @@ -175,7 +172,6 @@ def scaled?
scaler.present? || Area.derived?(area_code)
end


# Public: The year on which the analysis for the scenario's area is based.
#
# Returns an integer.
Expand All @@ -192,7 +188,7 @@ def years

# Creates a scenario from a yml_file. Used by mech turk.
def self.create_from_file(yml_file)
settings = YAML::load(File.read(yml_file))['settings']
settings = YAML.load(File.read(yml_file))['settings']
Scenario.default(settings)
end

Expand Down Expand Up @@ -220,7 +216,7 @@ def self.create_from_json(json_data)
def gql(options = {}, &block)
unless @gql

if block_given?
if block
@gql = Gql::Gql.new(self, &block)
else
@gql = Gql::Gql.new(self)
Expand Down Expand Up @@ -248,7 +244,7 @@ def scenario_id=(preset_id)
# a identifier for the scenario selector drop down in data.
# => "#32341 - nl 2040 (2011-01-11)"
def identifier
"##{id} - #{area_code} #{end_year} (#{created_at.strftime("%m-%d %H:%M")})"
"##{id} - #{area_code} #{end_year} (#{created_at.strftime('%m-%d %H:%M')})"
end

# shortcut to run GQL queries
Expand Down Expand Up @@ -285,18 +281,16 @@ def outdated?
#
# Returns a float.
def input_value(input)
unless input.respond_to?(:key)
raise ArgumentError, "#{ input.inspect } is not an input"
end
raise ArgumentError, "#{input.inspect} is not an input" unless input.respond_to?(:key)

user_values[input.key] ||
balanced_values[input.key] ||
input.start_value_for(self)
end

def heat_network_order(temperature = :mt)
heat_network_orders.find_by(temperature: temperature) ||
HeatNetworkOrder.default(scenario_id: id, temperature: temperature)
heat_network_orders.find_by(temperature:) ||
HeatNetworkOrder.default(scenario_id: id, temperature:)
end

def forecast_storage_order
Expand Down Expand Up @@ -341,11 +335,11 @@ def clone_should_be_private?(actor)
end

def owner?(user)
scenario_users.find_by(user: user, role_id: User::ROLES.key(:scenario_owner))
scenario_users.find_by(user:, role_id: User::ROLES.key(:scenario_owner))
end

def collaborator?(user)
scenario_users.find_by(user: user, role_id: User::ROLES.key(:scenario_collaborator)..)
scenario_users.find_by(user:, role_id: User::ROLES.key(:scenario_collaborator)..)
end

# Convenience method to quickly set the owner for a scenario, e.g. when creating it as
Expand All @@ -357,7 +351,7 @@ def user=(user)

ScenarioUser.create(
scenario: self,
user: user,
user:,
role_id: User::ROLES.key(:scenario_owner)
)
end
Expand All @@ -371,6 +365,7 @@ def delete_all_users

def copy_preset_roles
return unless parent
return if copy_roles_from_saved_scenario

parent.scenario_users.each do |preset_user|
if (existing_user = scenario_users.find_by(user: preset_user.user))
Expand All @@ -384,6 +379,38 @@ def copy_preset_roles

private

def copy_roles_from_saved_scenario
saved_scenario_id = fetch_saved_scenario_id_for(parent.id)
return false unless saved_scenario_id

users_data = fetch_saved_scenario_users(saved_scenario_id)
return false unless users_data

users_data.each do |user_data|
scenario_users.create(
user_id: user_data['user_id'],
user_email: user_data['user_email'],
role_id: user_data['role_id']
)
end

true
rescue StandardError
false
end

def fetch_saved_scenario_id_for(scenario_id)
scenario = Scenario.find_by(id: scenario_id)
scenario&.metadata&.dig('saved_scenario_id')
end

def fetch_saved_scenario_users(saved_scenario_id)
response = ETEngine::MyEtm.client.get("/api/v1/saved_scenarios/#{saved_scenario_id}/users")
response.success? ? response.body : nil
rescue Faraday::Error
nil
end

# Validation method for when a user sets their metadata.
def validate_metadata_size
errors.add(:metadata, 'can not exceed 64Kb') if metadata.to_s.bytesize > 64.kilobytes
Expand Down
7 changes: 5 additions & 2 deletions app/views/inspect/scenarios/show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@
%td
- if @scenario.scenario_users.present?
- @scenario.scenario_users.each do |scenario_user|
- if scenario_user.user
= link_to("#{scenario_user.user.name} (#{User::ROLES[scenario_user.role_id].to_s.humanize})", user_path(scenario_user.user))
%div
- if scenario_user.user
= link_to("#{scenario_user.user.name} (#{User::ROLES[scenario_user.role_id].to_s.humanize})", user_path(scenario_user.user))
- elsif scenario_user.user_email
= "#{scenario_user.user_email} (#{User::ROLES[scenario_user.role_id].to_s.humanize})"
- else
%span.muted No owner
%tr
Expand Down
12 changes: 11 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
post :create
put :update
delete :destroy
delete :destroy_all
end
end

Expand All @@ -112,7 +113,16 @@
end
end

resources :saved_scenarios, except: %i[new edit]
resources :saved_scenarios, except: %i[new edit] do
resources :users, only: %i[index create update destroy], controller: 'saved_scenario_users' do
collection do
post :create
put :update
delete :destroy
end
end
end

resources :collections, except: %i[new edit]

# Redirecting old transition paths routes to collections
Expand Down
18 changes: 18 additions & 0 deletions lib/etengine/my_etm.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module ETEngine
# Handles communication with MyETM API.
module MyEtm
module_function

# Returns a Faraday client configured to communicate with MyETM.
def client
@client ||= Faraday.new(url: Settings.identity.issuer) do |conn|
conn.request(:json)
conn.response(:json)
conn.response(:raise_error)
conn.options.timeout = 5
end
end
end
end
Loading