From 1720e0e8148c68638a0f9ead5f2358de10258a55 Mon Sep 17 00:00:00 2001 From: timalces Date: Fri, 9 Feb 2024 16:43:52 +0000 Subject: [PATCH 01/14] added choosing team to new cluster workflow --- .../components/_new_cluster_form.scss | 8 +++--- app/controllers/clusters_controller.rb | 26 +++++++++++++++---- app/jobs/create_cluster_job.rb | 3 ++- app/jobs/get_cloud_assets_job.rb | 8 +++--- app/models/ability.rb | 5 ++-- app/models/cluster.rb | 23 +++++++++++++--- app/models/team.rb | 1 + app/views/cluster_types/_card.html.erb | 2 +- app/views/clusters/choose_team.html.erb | 20 ++++++++++++++ app/views/clusters/new.html.erb | 5 ++-- config/routes.rb | 6 ++++- 11 files changed, 85 insertions(+), 22 deletions(-) create mode 100644 app/views/clusters/choose_team.html.erb diff --git a/app/assets/stylesheets/components/_new_cluster_form.scss b/app/assets/stylesheets/components/_new_cluster_form.scss index 53f79dd1f..ba7e97f85 100644 --- a/app/assets/stylesheets/components/_new_cluster_form.scss +++ b/app/assets/stylesheets/components/_new_cluster_form.scss @@ -1,7 +1,7 @@ -#new_cluster { - .formItem { - margin-bottom: 1rem; - } +.new_cluster { + .formItem { + margin-bottom: 1rem; + } .new-cluster-field { margin-bottom: 0; diff --git a/app/controllers/clusters_controller.rb b/app/controllers/clusters_controller.rb index 16c8aa3ee..3f7d18388 100644 --- a/app/controllers/clusters_controller.rb +++ b/app/controllers/clusters_controller.rb @@ -1,4 +1,16 @@ class ClustersController < ApplicationController + def choose_team + authorize! :create, Cluster + @cluster_type = ClusterType.find_by_foreign_id!(params[:cluster_type_foreign_id]) + @valid_teams = current_user.teams.meets_cluster_credit_requirement + unless @valid_teams.exists? + flash[:alert] = "You must belong to a team with at least #{Rails.application.config.cluster_credit_requirement} credits" + redirect_to cluster_types_path + end + @unavailable_teams = current_user.teams.where.not(id: @valid_teams.pluck(:id)) + @all_teams = current_user.teams + end + def new authorize! :create, Cluster @cloud_service_config = CloudServiceConfig.first @@ -9,6 +21,7 @@ def new end @cluster_type = ClusterType.find_by_foreign_id!(params[:cluster_type_foreign_id]) + @team = current_user.teams.find(params[:team_id]) use_cache = params[:use_cache] != "false" result = SyncIndividualClusterTypeJob.perform_now(@cloud_service_config, @cluster_type, use_cache) unless result.success? @@ -21,25 +34,28 @@ def new end def create - authorize! :create, Cluster @cloud_service_config = CloudServiceConfig.first @cluster_type = ClusterType.find_by_foreign_id!(params[:cluster_type_foreign_id]) + @team = Team.find(permitted_params[:team_id]) selections = (permitted_params[:selections] || {}).transform_values { |v| ActiveModel::Type::Boolean.new.cast(v) }.to_h @cluster = Cluster.new( cluster_type: @cluster_type, name: permitted_params[:name], cluster_params: permitted_params[:cluster_params], selections: selections, + team: @team ) + authorize! :create, @cluster + if @cloud_service_config.nil? flash.now.alert = "Unable to send cluster configuration: cloud environment config not set. Please contact an admin" render action: :new return end - unless current_user.project_id - flash.now.alert = "Unable to send cluster configuration: you do not yet have a project id. " \ + unless @team.project_id + flash.now.alert = "Unable to send cluster configuration: your team does not yet have a project id. " \ "This will be added automatically shortly." render action: :new return @@ -75,11 +91,11 @@ def permitted_params valid_selections = @cluster_type.field_groups .select { |group| group["optional"].present? } .map { |group| group["optional"]["name"] } - params.require(:cluster).permit(:name, cluster_params: @cluster_type.fields.keys, selections: valid_selections) + params.require(:cluster).permit(:name, :team_id, cluster_params: @cluster_type.fields.keys, selections: valid_selections) end def set_cloud_assets - result = GetCloudAssetsJob.perform_now(@cloud_service_config, current_user) + result = GetCloudAssetsJob.perform_now(@cloud_service_config, current_user, @team) if result.success? @cloud_assets = result.assets else diff --git a/app/jobs/create_cluster_job.rb b/app/jobs/create_cluster_job.rb index 0e5a21d80..14c426b81 100644 --- a/app/jobs/create_cluster_job.rb +++ b/app/jobs/create_cluster_job.rb @@ -50,6 +50,7 @@ def test_stubs def call response = connection.post(path, body) + Rails.logger.info(response) Result.new(response.success?, response.reason_phrase || "Unknown error", response.status) rescue Faraday::BadRequestError @@ -96,7 +97,7 @@ def cloud_env_details auth_url: @cloud_service_config.internal_auth_url, user_id: @user.cloud_user_id, password: @user.foreign_password, - project_id: @user.project_id + project_id: @cluster.team.project_id } end diff --git a/app/jobs/get_cloud_assets_job.rb b/app/jobs/get_cloud_assets_job.rb index 76c814828..9d584534e 100644 --- a/app/jobs/get_cloud_assets_job.rb +++ b/app/jobs/get_cloud_assets_job.rb @@ -5,10 +5,11 @@ class GetCloudAssetsJob < ApplicationJob queue_as :default - def perform(cloud_service_config, user, **options) + def perform(cloud_service_config, user, team, **options) runner = Runner.new( cloud_service_config: cloud_service_config, user: user, + team: team, logger: logger, **options ) @@ -34,8 +35,9 @@ def error_message end class Runner < HttpRequests::Faraday::JobRunner - def initialize(user:, **kwargs) + def initialize(user:, team:, **kwargs) @user = user + @team = team super(**kwargs) end @@ -68,7 +70,7 @@ def params auth_url: @cloud_service_config.internal_auth_url, user_id: @user.cloud_user_id, password: @user.foreign_password, - project_id: @user.project_id + project_id: @team.project_id } end end diff --git a/app/models/ability.rb b/app/models/ability.rb index dc26b2f32..64c5c561f 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -7,7 +7,7 @@ def initialize(user) end def enough_credits_to_create_cluster? - @user.teams.where("credits >= ?", Rails.application.config.cluster_credit_requirement).exists? + @user.teams.meets_cluster_credit_requirement.exists? end private @@ -47,7 +47,8 @@ def non_root_abilities can :manage, RackviewPreset, user: @user can :read, ClusterType - can :create, Cluster if enough_credits_to_create_cluster? + can :new, Cluster if enough_credits_to_create_cluster? + can :create, Cluster, team_id: @user.teams.meets_cluster_credit_requirement.pluck(:id) can :read, KeyPair, user: @user can :create, KeyPair, user: @user diff --git a/app/models/cluster.rb b/app/models/cluster.rb index 1602549a7..22fd3fe0b 100644 --- a/app/models/cluster.rb +++ b/app/models/cluster.rb @@ -8,6 +8,7 @@ class Cluster #################################### attr_accessor :cluster_type + attr_accessor :team attr_accessor :name attr_accessor :fields attr_accessor :field_groups @@ -20,7 +21,12 @@ class Cluster #################################### validates :cluster_type, - presence: true + presence: true + + validates :team, + presence: true + + validate :team_has_enough_credits? validates :name, presence: true, @@ -36,8 +42,9 @@ class Cluster # #################################### - def initialize(cluster_type:, name: nil, cluster_params: nil, selections: {}) + def initialize(cluster_type:, team: nil, name: nil, cluster_params: nil, selections: {}) @cluster_type = cluster_type + @team = team @name = name @selections = selections @field_groups = Cluster::FieldGroups.new(self, cluster_type.field_groups, cluster_type.fields) @@ -49,6 +56,10 @@ def type_id @cluster_type.foreign_id end + def team_id + @team&.id + end + def field_values {}.tap do |field_values| fields.each do |field| @@ -83,4 +94,10 @@ def valid_fields? end end end -end + + def team_has_enough_credits? + if team_id && Team.meets_cluster_credit_requirement.where(id: team_id).empty? + errors.add(:team, "Has insufficient credits to launch a cluster") + end + end +end \ No newline at end of file diff --git a/app/models/team.rb b/app/models/team.rb index e2b5a3cee..ef38e6c03 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -1,6 +1,7 @@ class Team < ApplicationRecord include Searchable default_search_scope :name + scope :meets_cluster_credit_requirement, -> { where("credits >= ?", Rails.application.config.cluster_credit_requirement) } normalizes :project_id, with: -> project_id { project_id.strip } normalizes :name, with: -> name { name.strip } diff --git a/app/views/cluster_types/_card.html.erb b/app/views/cluster_types/_card.html.erb index 9823e497d..2ba290592 100644 --- a/app/views/cluster_types/_card.html.erb +++ b/app/views/cluster_types/_card.html.erb @@ -9,7 +9,7 @@
<% if current_user.can?(:create, Cluster) %> - <%= link_to "Select", new_cluster_type_cluster_path(cluster_type) %> + <%= link_to "Select", team_cluster_type_clusters_path(cluster_type) %> <% else %> <% title = if current_ability.enough_credits_to_create_cluster? "You do not have permission to create clusters" diff --git a/app/views/clusters/choose_team.html.erb b/app/views/clusters/choose_team.html.erb new file mode 100644 index 000000000..caa33336a --- /dev/null +++ b/app/views/clusters/choose_team.html.erb @@ -0,0 +1,20 @@ +<% set_title 'Create cluster - choose team' -%> +<% content_for(:side_content) do %> + <%= render 'actions' %> +<% end %> + +

Choose team for cluster <%= @cluster_type.name %>

+

<%= @cluster_type.description %>

+ +<%= form_with(url: new_cluster_type_cluster_path(@cluster_type), method: :get, class: "new_cluster") do |form| %> +
+
+ <%= form.label :team_id, "Team", class: "required_field" %> + <%= form.collection_select :team_id, @all_teams, :id, :name, {disabled: @unavailable_teams.pluck(:id)}, required: true %> +
+
+ Must have at least <%= Rails.application.config.cluster_credit_requirement %> credits +
+
+ +<% end %> diff --git a/app/views/clusters/new.html.erb b/app/views/clusters/new.html.erb index 3d2a0e729..5ea097d9d 100644 --- a/app/views/clusters/new.html.erb +++ b/app/views/clusters/new.html.erb @@ -2,15 +2,16 @@ <%= javascript_import_module_tag "clusters/new" %> <% end %> -<% set_title 'Configure cluster' -%> +<% set_title 'Create cluster - configure cluster' -%> <% content_for(:side_content) do %> <%= render 'actions' %> <% end %> -

Configure details for cluster <%= @cluster_type.name %>

+

Configure details for cluster <%= @cluster_type.name %> in team <%= @team.name %>

<%= @cluster_type.description %>

<%= form_for(@cluster, url: cluster_type_clusters_path(@cluster_type)) do |f| %> + <%= f.hidden_field :team_id %> <%= cell('cluster_form_errors').(:show, @cluster) %> <%= cell('cluster_form_name').(:show, @cluster, f) %> diff --git a/config/routes.rb b/config/routes.rb index 06189d7bf..122a73032 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -37,7 +37,11 @@ end end resources :cluster_types, path: 'cluster-types', only: [:index], param: :foreign_id do - resources :clusters, only: [:new, :create] + resources :clusters, only: [:new, :create] do + collection do + get '/team', to: 'clusters#choose_team' + end + end end end From d1534abb44ad406d2c28c9055f8753e07556d289 Mon Sep 17 00:00:00 2001 From: timalces Date: Fri, 9 Feb 2024 18:21:30 +0000 Subject: [PATCH 02/14] added create cluster action from teams index page --- .../components/_cluster_types_view.scss | 8 ++++- app/controllers/cluster_types_controller.rb | 1 + app/controllers/clusters_controller.rb | 2 +- app/jobs/create_cluster_job.rb | 2 +- app/views/cluster_types/_card.html.erb | 33 ++++++++++++++----- app/views/cluster_types/index.html.erb | 6 ++-- app/views/teams/index.html.erb | 1 + 7 files changed, 38 insertions(+), 15 deletions(-) diff --git a/app/assets/stylesheets/components/_cluster_types_view.scss b/app/assets/stylesheets/components/_cluster_types_view.scss index 1ea4b874b..dcf9d36bc 100644 --- a/app/assets/stylesheets/components/_cluster_types_view.scss +++ b/app/assets/stylesheets/components/_cluster_types_view.scss @@ -1,9 +1,10 @@ .cluster-type-card { - width: 19rem; + width: 22rem; border: darkgray 1px solid; padding: 0.5em; margin-left: 0.5rem; margin-right: 0.5rem; + margin-bottom: 1rem; .card-divider { border-bottom: darkgray 1px solid; @@ -15,3 +16,8 @@ font-weight: bold; } } + +.cluster-types { + display: flex; + flex-wrap: wrap; +} diff --git a/app/controllers/cluster_types_controller.rb b/app/controllers/cluster_types_controller.rb index 3aa9808b5..5b340be38 100644 --- a/app/controllers/cluster_types_controller.rb +++ b/app/controllers/cluster_types_controller.rb @@ -8,5 +8,6 @@ def index result = SyncAllClusterTypesJob.perform_now(@cloud_service_config, use_cache) flash.now.alert = result.error_message unless result.success? end + @team = Team.find(params[:team_id]) if params[:team_id] end end diff --git a/app/controllers/clusters_controller.rb b/app/controllers/clusters_controller.rb index 3f7d18388..f74059a20 100644 --- a/app/controllers/clusters_controller.rb +++ b/app/controllers/clusters_controller.rb @@ -30,7 +30,7 @@ def new return end set_cloud_assets - @cluster = Cluster.new(cluster_type: @cluster_type) + @cluster = Cluster.new(cluster_type: @cluster_type, team: @team) end def create diff --git a/app/jobs/create_cluster_job.rb b/app/jobs/create_cluster_job.rb index 14c426b81..d71538520 100644 --- a/app/jobs/create_cluster_job.rb +++ b/app/jobs/create_cluster_job.rb @@ -78,7 +78,7 @@ def body { cloud_env: cloud_env_details, cluster: cluster_details, - billing_account_id: @user.billing_acct_id, + billing_account_id: @cluster.team.billing_acct_id, middleware_url: @cloud_service_config.user_handler_base_url, } end diff --git a/app/views/cluster_types/_card.html.erb b/app/views/cluster_types/_card.html.erb index 2ba290592..c4202637a 100644 --- a/app/views/cluster_types/_card.html.erb +++ b/app/views/cluster_types/_card.html.erb @@ -8,16 +8,31 @@

<%= cluster_type.description %>

- <% if current_user.can?(:create, Cluster) %> - <%= link_to "Select", team_cluster_type_clusters_path(cluster_type) %> + <% if team %> + <% cluster = Cluster.new(cluster_type: cluster_type, team: team) %> + <% if current_user.can?(:create, cluster) %> + <%= link_to "Select", new_cluster_type_cluster_path(cluster_type, team_id: team.id) %> + <% else %> + <% title = if Team.meets_cluster_credit_requirement.where(id: team.id).exists? + "You do not have permission to create clusters" + else + "Insufficient credits. Your chosen team must have at least #{Rails.application.config.cluster_credit_requirement} credits to create a cluster" + end + %> + Select + <% end %> <% else %> - <% title = if current_ability.enough_credits_to_create_cluster? - "You do not have permission to create clusters" - else - "Insufficient credits. You must belong to a team with at least #{Rails.application.config.cluster_credit_requirement} credits to create a cluster" - end - %> - Select + <% if current_user.can?(:create, Cluster) %> + <%= link_to "Select", team_cluster_type_clusters_path(cluster_type) %> + <% else %> + <% title = if current_ability.enough_credits_to_create_cluster? + "You do not have permission to create clusters" + else + "Insufficient credits. You must belong to a team with at least #{Rails.application.config.cluster_credit_requirement} credits to create a cluster" + end + %> + Select + <% end %> <% end %>
diff --git a/app/views/cluster_types/index.html.erb b/app/views/cluster_types/index.html.erb index 4766bd9cc..fd99e42f8 100644 --- a/app/views/cluster_types/index.html.erb +++ b/app/views/cluster_types/index.html.erb @@ -1,4 +1,4 @@ -<% set_title "Select cluster type" %> +<% set_title "Select cluster type #{ "for team #{@team.name}" if @team}" %> <% content_for(:side_content) do %> <%= render 'actions' %> <% end %> @@ -15,9 +15,9 @@ <% elsif @cluster_types.empty? %>

There are no cluster types available at present.

<% else %> -
+
<% @cluster_types.each do |cluster_type| %> - <%= render partial: 'card', object: cluster_type %> + <%= render partial: 'card', object: cluster_type, locals: { team: @team } %> <% end %>
<% end %> diff --git a/app/views/teams/index.html.erb b/app/views/teams/index.html.erb index 5fbf236ae..072defce2 100644 --- a/app/views/teams/index.html.erb +++ b/app/views/teams/index.html.erb @@ -37,6 +37,7 @@ <% actions.add_with_auth can: :manage, on: TeamRole.new(team_id: team.id), title: 'Manage Users', path: team_team_roles_path(team) %> <% actions.add_with_auth can: :read, on: Invoice.new(account: team), title: 'View Invoices', path: team_invoices_path(team) %> <% actions.add_with_auth can: :create, on: CreditDeposit.new(team: team), title: 'Add Credits', path: new_team_credit_deposit_path(team) %> + <% actions.add_with_auth can: :create, on: Cluster.new(team: team, cluster_type: ClusterType.new), title: 'Create Cluster', path: cluster_types_path(team_id: team.id) %> <% actions.add_with_auth(can: :destroy, on: team, title: 'Delete', From b83d4ecec16767c12abbd239b9d16b7600b1b36d Mon Sep 17 00:00:00 2001 From: timalces Date: Mon, 12 Feb 2024 11:22:22 +0000 Subject: [PATCH 03/14] updated tests --- app/jobs/get_cloud_assets_job.rb | 2 +- spec/factories/clusters.rb | 1 + spec/jobs/create_cluster_job_spec.rb | 8 ++++---- spec/models/cluster_spec.rb | 10 ++++++++++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/app/jobs/get_cloud_assets_job.rb b/app/jobs/get_cloud_assets_job.rb index 9d584534e..fdde100c0 100644 --- a/app/jobs/get_cloud_assets_job.rb +++ b/app/jobs/get_cloud_assets_job.rb @@ -1,7 +1,7 @@ require 'faraday' # GetCloudAssetsJob retrieves cloud assets from cluster builder such as the -# list of flavors, images and networks availabel to the given user. +# list of flavors, images and networks available to the given user. class GetCloudAssetsJob < ApplicationJob queue_as :default diff --git a/spec/factories/clusters.rb b/spec/factories/clusters.rb index bed7509e6..40ef470d6 100644 --- a/spec/factories/clusters.rb +++ b/spec/factories/clusters.rb @@ -2,6 +2,7 @@ factory :cluster, class: 'Cluster' do name { 'mycluster' } cluster_type { create(:cluster_type) } + team { create(:team, :with_openstack_details, credits: 1000) } initialize_with { new(**attributes) } end diff --git a/spec/jobs/create_cluster_job_spec.rb b/spec/jobs/create_cluster_job_spec.rb index c3987b134..c9aec287a 100644 --- a/spec/jobs/create_cluster_job_spec.rb +++ b/spec/jobs/create_cluster_job_spec.rb @@ -87,13 +87,13 @@ class << subject "auth_url" => cloud_service_config.internal_auth_url, "user_id" => user.cloud_user_id, "password" => user.foreign_password, - "project_id" => user.project_id + "project_id" => cluster.team.project_id }) end - it "contains the users billing account id" do - expect(user.billing_acct_id).not_to be_nil - expect(subject[:billing_account_id]).to eq user.billing_acct_id + it "contains the cluster's team's billing account id" do + expect(cluster.team.billing_acct_id).not_to be_nil + expect(subject[:billing_account_id]).to eq cluster.team.billing_acct_id end it "contains the middleware url" do diff --git a/spec/models/cluster_spec.rb b/spec/models/cluster_spec.rb index 60a312916..b888cd3ec 100644 --- a/spec/models/cluster_spec.rb +++ b/spec/models/cluster_spec.rb @@ -22,4 +22,14 @@ cluster_params["clustername"] = nil expect(subject).to have_error("Cluster name", "can't be blank") end + + it "is not valid without a team" do + subject.team = nil + expect(subject).to have_error(:team, :blank) + end + + it "is not valid if team has insufficient credits" do + subject.team.update(credits: 0) + expect(subject).to have_error(:team, "Has insufficient credits to launch a cluster") + end end From f81c843d38301b37407efb8a0c1dd1a485a24402 Mon Sep 17 00:00:00 2001 From: timalces Date: Tue, 13 Feb 2024 15:24:59 +0000 Subject: [PATCH 04/14] refactored to choose team on cluster types page --- .../components/_cluster_types_view.scss | 11 ++++++++ app/controllers/cluster_types_controller.rb | 3 ++ app/controllers/clusters_controller.rb | 12 -------- app/javascript/cluster_types/index.js | 16 +++++++++++ app/views/cluster_types/_card.html.erb | 27 +++++++++--------- app/views/cluster_types/index.html.erb | 28 ++++++++++++++++++- config/importmap.rb | 1 + config/routes.rb | 6 +--- 8 files changed, 73 insertions(+), 31 deletions(-) create mode 100644 app/javascript/cluster_types/index.js diff --git a/app/assets/stylesheets/components/_cluster_types_view.scss b/app/assets/stylesheets/components/_cluster_types_view.scss index dcf9d36bc..5c01386e4 100644 --- a/app/assets/stylesheets/components/_cluster_types_view.scss +++ b/app/assets/stylesheets/components/_cluster_types_view.scss @@ -17,6 +17,17 @@ } } +.choose-team { + max-width: 25rem; + margin-bottom: 2rem; + margin-left: 0.8rem; + margin-top: 1rem; + + select { + margin-bottom: 0; + } +} + .cluster-types { display: flex; flex-wrap: wrap; diff --git a/app/controllers/cluster_types_controller.rb b/app/controllers/cluster_types_controller.rb index 5b340be38..40377133c 100644 --- a/app/controllers/cluster_types_controller.rb +++ b/app/controllers/cluster_types_controller.rb @@ -8,6 +8,9 @@ def index result = SyncAllClusterTypesJob.perform_now(@cloud_service_config, use_cache) flash.now.alert = result.error_message unless result.success? end + @valid_teams = current_user.teams.meets_cluster_credit_requirement + @unavailable_teams = current_user.teams.where.not(id: @valid_teams.pluck(:id)) + @all_teams = current_user.teams @team = Team.find(params[:team_id]) if params[:team_id] end end diff --git a/app/controllers/clusters_controller.rb b/app/controllers/clusters_controller.rb index f74059a20..2b64fb941 100644 --- a/app/controllers/clusters_controller.rb +++ b/app/controllers/clusters_controller.rb @@ -1,16 +1,4 @@ class ClustersController < ApplicationController - def choose_team - authorize! :create, Cluster - @cluster_type = ClusterType.find_by_foreign_id!(params[:cluster_type_foreign_id]) - @valid_teams = current_user.teams.meets_cluster_credit_requirement - unless @valid_teams.exists? - flash[:alert] = "You must belong to a team with at least #{Rails.application.config.cluster_credit_requirement} credits" - redirect_to cluster_types_path - end - @unavailable_teams = current_user.teams.where.not(id: @valid_teams.pluck(:id)) - @all_teams = current_user.teams - end - def new authorize! :create, Cluster @cloud_service_config = CloudServiceConfig.first diff --git a/app/javascript/cluster_types/index.js b/app/javascript/cluster_types/index.js new file mode 100644 index 000000000..271b3b1d8 --- /dev/null +++ b/app/javascript/cluster_types/index.js @@ -0,0 +1,16 @@ +document.addEventListener("DOMContentLoaded", function() { + const teamSelect = document.querySelector("#choose_cluster_team"); + const typeLinks = document.querySelectorAll(".cluster-type-link"); + + if(teamSelect && !teamSelect.disabled && typeLinks.length > 0) { + teamSelect.addEventListener("change", function(event) { + let selectedTeamId = event.target.value; + + typeLinks.forEach(function(link) { + link.href = `${link.dataset.baseTargetUrl}?team_id=${selectedTeamId}`; + link.classList.remove("disabled-cluster-link"); + link.title = ""; + }); + }); + } +}); diff --git a/app/views/cluster_types/_card.html.erb b/app/views/cluster_types/_card.html.erb index c4202637a..2472f7c13 100644 --- a/app/views/cluster_types/_card.html.erb +++ b/app/views/cluster_types/_card.html.erb @@ -8,10 +8,12 @@

<%= cluster_type.description %>

+ <% disabled = true %> <% if team %> <% cluster = Cluster.new(cluster_type: cluster_type, team: team) %> <% if current_user.can?(:create, cluster) %> - <%= link_to "Select", new_cluster_type_cluster_path(cluster_type, team_id: team.id) %> + <% disabled = false %> + <% title = "" %> <% else %> <% title = if Team.meets_cluster_credit_requirement.where(id: team.id).exists? "You do not have permission to create clusters" @@ -19,20 +21,19 @@ "Insufficient credits. Your chosen team must have at least #{Rails.application.config.cluster_credit_requirement} credits to create a cluster" end %> - Select <% end %> <% else %> - <% if current_user.can?(:create, Cluster) %> - <%= link_to "Select", team_cluster_type_clusters_path(cluster_type) %> - <% else %> - <% title = if current_ability.enough_credits_to_create_cluster? - "You do not have permission to create clusters" - else - "Insufficient credits. You must belong to a team with at least #{Rails.application.config.cluster_credit_requirement} credits to create a cluster" - end - %> - Select - <% end %> + <% title = if !current_user.can?(:create, Cluster) + "You do not have permission to create this cluster" + elsif !available_teams + "Unable to create a cluster - you must belong to a team with at least #{Rails.application.config.cluster_credit_requirement} credits to create a cluster" + else + "Please select a team" + end + %> <% end %> + <%= link_to "Select", disabled ? "#" : new_cluster_type_cluster_path(cluster_type, team_id: team&.id), + { class: "cluster-type-link #{"disabled-cluster-link" if disabled}", title: title, + data: { base_target_url: new_cluster_type_cluster_path(cluster_type) } } %>
diff --git a/app/views/cluster_types/index.html.erb b/app/views/cluster_types/index.html.erb index fd99e42f8..38852f790 100644 --- a/app/views/cluster_types/index.html.erb +++ b/app/views/cluster_types/index.html.erb @@ -1,4 +1,8 @@ <% set_title "Select cluster type #{ "for team #{@team.name}" if @team}" %> +<% content_for(:head) do %> + <%= javascript_import_module_tag "cluster_types/index" %> +<% end %> + <% content_for(:side_content) do %> <%= render 'actions' %> <% end %> @@ -15,9 +19,31 @@ <% elsif @cluster_types.empty? %>

There are no cluster types available at present.

<% else %> +
+
+
+ <%= label_tag :team_id, "Team", class: "required_field" %> + + +
+
+ Must have at least <%= Rails.application.config.cluster_credit_requirement %> credits +
+
+
+
+ <% available_teams = @valid_teams.exists? %> <% @cluster_types.each do |cluster_type| %> - <%= render partial: 'card', object: cluster_type, locals: { team: @team } %> + <%= render partial: 'card', object: cluster_type, locals: { team: @team, available_teams: available_teams } %> <% end %>
<% end %> diff --git a/config/importmap.rb b/config/importmap.rb index 2995ae5ee..841c2285e 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -7,6 +7,7 @@ pin "key_pairs/new", to: "key_pairs/new.js" pin "metrics/index", to: "metrics/index.js" pin "clusters/new", to: "clusters/new.js" +pin "cluster_types/index", to: "cluster_types/index.js" # Utility Javascripts. pin "Profiler", to: "canvas/irv/NullProfiler.js" diff --git a/config/routes.rb b/config/routes.rb index 122a73032..06189d7bf 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -37,11 +37,7 @@ end end resources :cluster_types, path: 'cluster-types', only: [:index], param: :foreign_id do - resources :clusters, only: [:new, :create] do - collection do - get '/team', to: 'clusters#choose_team' - end - end + resources :clusters, only: [:new, :create] end end From d8840b30d310012191b627b31d807d6623381565 Mon Sep 17 00:00:00 2001 From: timalces Date: Tue, 13 Feb 2024 16:29:37 +0000 Subject: [PATCH 05/14] updated action sidebars --- app/models/cluster.rb | 2 +- app/views/cluster_types/_actions.html.erb | 2 +- app/views/cluster_types/_card.html.erb | 4 ++-- app/views/cluster_types/index.html.erb | 2 +- app/views/clusters/_actions.html.erb | 4 ++-- app/views/clusters/choose_team.html.erb | 20 -------------------- 6 files changed, 7 insertions(+), 27 deletions(-) delete mode 100644 app/views/clusters/choose_team.html.erb diff --git a/app/models/cluster.rb b/app/models/cluster.rb index 22fd3fe0b..fca8c7f31 100644 --- a/app/models/cluster.rb +++ b/app/models/cluster.rb @@ -100,4 +100,4 @@ def team_has_enough_credits? errors.add(:team, "Has insufficient credits to launch a cluster") end end -end \ No newline at end of file +end diff --git a/app/views/cluster_types/_actions.html.erb b/app/views/cluster_types/_actions.html.erb index 7261c5e86..52267b51f 100644 --- a/app/views/cluster_types/_actions.html.erb +++ b/app/views/cluster_types/_actions.html.erb @@ -2,6 +2,6 @@ <%= render_lhm_actions("Cluster type actions") do |actions| - actions.add title: "Check for latest cluster types", path: cluster_types_path(use_cache: false) + actions.add title: "Check for latest cluster types", path: cluster_types_path(use_cache: false, team_id: @team) end %> diff --git a/app/views/cluster_types/_card.html.erb b/app/views/cluster_types/_card.html.erb index 2472f7c13..bcd260019 100644 --- a/app/views/cluster_types/_card.html.erb +++ b/app/views/cluster_types/_card.html.erb @@ -24,7 +24,7 @@ <% end %> <% else %> <% title = if !current_user.can?(:create, Cluster) - "You do not have permission to create this cluster" + "You do not have permission to create a cluster" elsif !available_teams "Unable to create a cluster - you must belong to a team with at least #{Rails.application.config.cluster_credit_requirement} credits to create a cluster" else @@ -34,6 +34,6 @@ <% end %> <%= link_to "Select", disabled ? "#" : new_cluster_type_cluster_path(cluster_type, team_id: team&.id), { class: "cluster-type-link #{"disabled-cluster-link" if disabled}", title: title, - data: { base_target_url: new_cluster_type_cluster_path(cluster_type) } } %> + data: { base_target_url: new_cluster_type_cluster_path(cluster_type) } } %> diff --git a/app/views/cluster_types/index.html.erb b/app/views/cluster_types/index.html.erb index 38852f790..9c0653ae4 100644 --- a/app/views/cluster_types/index.html.erb +++ b/app/views/cluster_types/index.html.erb @@ -1,4 +1,4 @@ -<% set_title "Select cluster type #{ "for team #{@team.name}" if @team}" %> +<% set_title "Select cluster type" %> <% content_for(:head) do %> <%= javascript_import_module_tag "cluster_types/index" %> <% end %> diff --git a/app/views/clusters/_actions.html.erb b/app/views/clusters/_actions.html.erb index 5744d6aae..f96e45e06 100644 --- a/app/views/clusters/_actions.html.erb +++ b/app/views/clusters/_actions.html.erb @@ -2,7 +2,7 @@ <%= render_lhm_actions("Cluster actions") do |actions| - actions.add title: "Re-select cluster type", path: cluster_types_path - actions.add title: "Check for cluster type updates", path: new_cluster_type_cluster_path(@cluster_type, use_cache: false) + actions.add title: "Re-select cluster type or team", path: cluster_types_path(team_id: @team) + actions.add title: "Check for cluster type updates", path: new_cluster_type_cluster_path(@cluster_type, use_cache: false, team_id: @team) end %> diff --git a/app/views/clusters/choose_team.html.erb b/app/views/clusters/choose_team.html.erb deleted file mode 100644 index caa33336a..000000000 --- a/app/views/clusters/choose_team.html.erb +++ /dev/null @@ -1,20 +0,0 @@ -<% set_title 'Create cluster - choose team' -%> -<% content_for(:side_content) do %> - <%= render 'actions' %> -<% end %> - -

Choose team for cluster <%= @cluster_type.name %>

-

<%= @cluster_type.description %>

- -<%= form_with(url: new_cluster_type_cluster_path(@cluster_type), method: :get, class: "new_cluster") do |form| %> -
-
- <%= form.label :team_id, "Team", class: "required_field" %> - <%= form.collection_select :team_id, @all_teams, :id, :name, {disabled: @unavailable_teams.pluck(:id)}, required: true %> -
-
- Must have at least <%= Rails.application.config.cluster_credit_requirement %> credits -
-
- -<% end %> From a46199d11432c5f8cdcf061863376b199bee5e19 Mon Sep 17 00:00:00 2001 From: timalces Date: Tue, 13 Feb 2024 18:13:54 +0000 Subject: [PATCH 06/14] list possible teams alphabetically --- app/controllers/cluster_types_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/cluster_types_controller.rb b/app/controllers/cluster_types_controller.rb index 40377133c..372039747 100644 --- a/app/controllers/cluster_types_controller.rb +++ b/app/controllers/cluster_types_controller.rb @@ -10,7 +10,7 @@ def index end @valid_teams = current_user.teams.meets_cluster_credit_requirement @unavailable_teams = current_user.teams.where.not(id: @valid_teams.pluck(:id)) - @all_teams = current_user.teams + @all_teams = current_user.teams.reorder(:name) @team = Team.find(params[:team_id]) if params[:team_id] end end From 6d6aa2b0cf0c1d6bc67e771486b529722b6262f4 Mon Sep 17 00:00:00 2001 From: timalces Date: Wed, 14 Feb 2024 11:15:55 +0000 Subject: [PATCH 07/14] better handling if no possible teams --- .../components/_cluster_types_view.scss | 7 +++- app/views/cluster_types/index.html.erb | 39 +++++++++++-------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/app/assets/stylesheets/components/_cluster_types_view.scss b/app/assets/stylesheets/components/_cluster_types_view.scss index 5c01386e4..fa0252cc4 100644 --- a/app/assets/stylesheets/components/_cluster_types_view.scss +++ b/app/assets/stylesheets/components/_cluster_types_view.scss @@ -18,11 +18,14 @@ } .choose-team { - max-width: 25rem; margin-bottom: 2rem; - margin-left: 0.8rem; + margin-left: 0.7rem; margin-top: 1rem; + .formItem { + max-width: 22rem; + } + select { margin-bottom: 0; } diff --git a/app/views/cluster_types/index.html.erb b/app/views/cluster_types/index.html.erb index 9c0653ae4..8505ef4b8 100644 --- a/app/views/cluster_types/index.html.erb +++ b/app/views/cluster_types/index.html.erb @@ -20,24 +20,29 @@

There are no cluster types available at present.

<% else %>
-
-
- <%= label_tag :team_id, "Team", class: "required_field" %> - - -
-
- Must have at least <%= Rails.application.config.cluster_credit_requirement %> credits + <% if @valid_teams.exists? %> +
+
+ <%= label_tag :team_id, "Team", class: "required_field" %> + +
+
+ Must have at least <%= Rails.application.config.cluster_credit_requirement %> credits +
-
+ <% else %> +

+ You must belong to a team with at least <%= Rails.application.config.cluster_credit_requirement %> credits to create a cluster. +

+ <% end %>
From 8ea1c1a760fb83fdff8e81f1e2b0472d09d0665d Mon Sep 17 00:00:00 2001 From: timalces Date: Wed, 21 Feb 2024 15:51:52 +0000 Subject: [PATCH 08/14] remove debugging --- app/jobs/create_cluster_job.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/jobs/create_cluster_job.rb b/app/jobs/create_cluster_job.rb index d71538520..67a43f9ab 100644 --- a/app/jobs/create_cluster_job.rb +++ b/app/jobs/create_cluster_job.rb @@ -50,7 +50,6 @@ def test_stubs def call response = connection.post(path, body) - Rails.logger.info(response) Result.new(response.success?, response.reason_phrase || "Unknown error", response.status) rescue Faraday::BadRequestError From 36189167d62a04881a05f331503fa19e13f94805 Mon Sep 17 00:00:00 2001 From: timalces Date: Tue, 27 Feb 2024 12:04:00 +0000 Subject: [PATCH 09/14] moved credits check out of ability and refactored actions to allow element title text --- .../components/_actions_dropdown.scss | 10 +++ app/cells/actions_cell.rb | 64 +++++++++---------- app/cells/resource_table/show.erb | 8 +-- app/cells/resource_table_cell.rb | 46 ++++++------- app/jobs/get_cloud_assets_job.rb | 2 +- app/models/ability.rb | 7 +- app/models/cluster.rb | 16 ++++- .../cloud_service_configs/_actions.html.erb | 4 +- app/views/cluster_types/_actions.html.erb | 2 +- app/views/cluster_types/index.html.erb | 2 +- app/views/clusters/_actions.html.erb | 4 +- .../interactive_rack_views/_actions.html.erb | 2 +- app/views/team_roles/index.html.erb | 4 +- app/views/teams/index.html.erb | 22 ++++--- app/views/users/index.html.erb | 12 ++-- config/navigation.rb | 2 +- 16 files changed, 116 insertions(+), 91 deletions(-) diff --git a/app/assets/stylesheets/components/_actions_dropdown.scss b/app/assets/stylesheets/components/_actions_dropdown.scss index 440c12846..0e7189ba4 100644 --- a/app/assets/stylesheets/components/_actions_dropdown.scss +++ b/app/assets/stylesheets/components/_actions_dropdown.scss @@ -42,6 +42,16 @@ width: 1rem; } } + + .disabled-action { + color: grey; + cursor: not-allowed; + + &:hover { + color: grey; + background-color: white; + } + } &.current a:before { @include icon-style; diff --git a/app/cells/actions_cell.rb b/app/cells/actions_cell.rb index 84b461217..1d93e40cd 100644 --- a/app/cells/actions_cell.rb +++ b/app/cells/actions_cell.rb @@ -17,8 +17,8 @@ class ActionsCell < Cell::ViewModel attr_reader :actions, :dropdown_id - def show(title, block, opts = {}) - # @title = title + def show(text, block, opts = {}) + # @text = text @is_dropdown = opts[:is_dropdown] || false @dropdown_id = opts[:dropdown_id] || 'drop' @side = opts[:side] || false @@ -73,11 +73,11 @@ def initialize(current_user, cell) @current_user = current_user @cell = cell end - + # # add # - # Adds an action based on options hash. Valid options are: title, path, html, side + # Adds an action based on options hash. Valid options are: text, path, html, side # # 'side' == true will render to the sidebar only. # @@ -85,32 +85,32 @@ def initialize(current_user, cell) # # e.g: # - # add(title: 'View', path: user_path(@user)) + # add(text: 'View', path: user_path(@user)) # # =>
  • View
  • # - def add(title_or_options, path = nil, &block) - if title_or_options.kind_of? String + def add(text_or_options, path = nil, &block) + if text_or_options.kind_of? String options = {} - title = title_or_options + text = text_or_options else - options = title_or_options - title = options[:title] + options = text_or_options + text = options[:text] path = options[:path] end html = (block_given? ? block.call : options[:html]) - opts = options.reject {|k, v| [:title, :html, :path, :can, :cannot, :on].include?(k)} + opts = options.reject {|k, v| [:text, :html, :path, :can, :cannot, :on].include?(k)} if html add_custom(html, opts) else - add_item(title, path, opts) + add_item(text, path, opts) end end def add_with_auth(options, &block) resource_or_class = options[:on] - + if options.has_key? :cannot action_name = options[:cannot] permission = :cannot? @@ -118,10 +118,10 @@ def add_with_auth(options, &block) action_name = options[:can] permission = :can? end - + opts = options.reject {|k, v| [:can, :cannot, :on].include?(k)} ability = @current_user.ability - + if ability.send(permission, action_name, resource_or_class) if block_given? add(opts, &block) @@ -130,32 +130,32 @@ def add_with_auth(options, &block) end end end - + # # divider # - # Adds a label with a title that separates menu items. + # Adds a label with content that separates menu items. # - def divider(title = nil) + def divider(text = nil) unless @dropdown_actions.empty? - divider = ActionDivider.new(title) + divider = ActionDivider.new(text) @dropdown_actions << divider end end - + private def side? @cell.side? end - + # # add_item # # Adds a basic link action # - def add_item(title, path, opts = {}) - action = Action.new(title, path, opts, @cell) + def add_item(text, path, opts = {}) + action = Action.new(text, path, opts, @cell) if side? @actions << action else @@ -178,12 +178,12 @@ def add_custom(html, opts = {}) end end end - + class ActionItem def li_opts @opts.reject {|k, v| [:path, :side, :method, :confirm].include?(k) } end - + def matches_request?(request) request.fullpath == @path unless @opts[:method].to_s == 'delete' end @@ -194,24 +194,24 @@ def divider? end class Action < ActionItem - attr_reader :title, :path, :opts - def initialize(title, path, opts = {}, cell) - @title = title + attr_reader :text, :path, :opts + def initialize(text, path, opts = {}, cell) + @text = text @path = path @opts = opts @cell = cell end def html - @cell.link_to @title, @path, @opts + @cell.link_to @text, @path, @opts end end class ActionDivider < ActionItem - attr_reader :title + attr_reader :text - def initialize(title) - @title = title + def initialize(text) + @text = text @path = nil @opts = {} end diff --git a/app/cells/resource_table/show.erb b/app/cells/resource_table/show.erb index 079c2474c..8ea939223 100644 --- a/app/cells/resource_table/show.erb +++ b/app/cells/resource_table/show.erb @@ -7,25 +7,25 @@ <% if column.select_all_column? %> - <%= column.title %> + <%= column.text %> <% elsif column.sortable? %> <% column.sortable_header(sort_column, sort_direction) do |sort, direction, current| %> - <%= link_to params.permit!.to_h.merge(sort: sort, direction: direction), title: column.tooltip do %> + <%= link_to params.permit!.to_h.merge(sort: sort, direction: direction), text: column.tooltip do %> <% if current %> <%= tag.span(class: [:current, direction]) %> <% else %> <%= tag.span(class: ['asc-desc']) %> <% end %> - <%= column.title %> + <%= column.text %> <% end %> <% end %> <% else %> - <%= column.title %> + <%= column.text %> <% end %> <% end %> diff --git a/app/cells/resource_table_cell.rb b/app/cells/resource_table_cell.rb index 3cbdd9902..4f4994f4c 100644 --- a/app/cells/resource_table_cell.rb +++ b/app/cells/resource_table_cell.rb @@ -62,8 +62,8 @@ def attribute_column(method, opts = {}, &block) add_column AttributeColumn.new(method, opts, &block), opts end - def custom_column(title, opts = {}, &block) - add_column CustomColumn.new(title, opts, &block), opts + def custom_column(text, opts = {}, &block) + add_column CustomColumn.new(text, opts, &block), opts end def select_all_column(opts = {}, &block) @@ -82,7 +82,7 @@ def on_empty_collection(&block) @table.empty_collection_block = block end - private + private def add_column(column, opts) return if opts[:suppress_if] @@ -99,9 +99,9 @@ def add_column(column, opts) # # ResourceTable # - # A model representing the actual table itself. + # A model representing the actual table itself. # - # Attributes of note: + # Attributes of note: # # => @id The html id of the table # => @items The items being rendered by the table @@ -129,7 +129,7 @@ def initialize(id, items, controller, opts = {}) establish_if_paginatable end - + def empty? @items.empty? end @@ -180,16 +180,16 @@ def establish_if_paginatable # # Column - # - # Base class for all types of column that could be added to the table. + # + # Base class for all types of column that could be added to the table. # class Column include ActionView::Helpers::TagHelper - attr_reader :title, :tooltip + attr_reader :text, :tooltip - def initialize(title, opts = {}, &block) - @title = opts.delete(:title) || title + def initialize(text, opts = {}, &block) + @text = opts.delete(:text) || text @tooltip = opts.delete(:tooltip) @html_class = opts.delete(:class) @opts = opts @@ -214,7 +214,7 @@ def html_class(index = nil) # render_content_for # # Simple accessor for the row/column's content. This *may* become more complex over time, for - # example if someone asks for all dates in tables to look a certain way, this is where you + # example if someone asks for all dates in tables to look a certain way, this is where you # would make this change in order to keep that logic out of the view. # def render_content_for(item) @@ -223,7 +223,7 @@ def render_content_for(item) def sortable? @opts[:sortable] == true - end + end # If this table is sortable, this yields the data required to render the sortable header that the # user clicks on. The `yield`ed values are: @@ -235,9 +235,9 @@ def sortable? def sortable_header(current_sort_column, current_sort_direction) sort_expression = sort_column.to_s is_current = sort_expression == current_sort_column - sort_order = (is_current && current_sort_direction == "asc") ? "desc" : "asc" + sort_order = (is_current && current_sort_direction == "asc") ? "desc" : "asc" - yield sort_expression, sort_order, is_current + yield sort_expression, sort_order, is_current end # Will either be the method name (in the case of attribute columns) or will be @@ -254,13 +254,13 @@ def select_all_column? # # CustomColumn - # + # # This is a basic column type that just yields the item back to the view. # class CustomColumn < Column - def initialize(title, opts, &block) - super(title.to_s, opts, &block) + def initialize(text, opts, &block) + super(text.to_s, opts, &block) end def render_content_for(item) @@ -275,7 +275,7 @@ def render_content_for(item) # # AttributeColumn - # + # # This column type will call a given method on the item, and then yield the # result of that method as well as the item back to the view. # @@ -291,7 +291,7 @@ def render_content_for(item) if @block @block.call item, item.send(@method) else - item.send(@method) + item.send(@method) end rescue Exception => e raise "Tried to call method '#{@method}' on #{item}: #{e.message}" @@ -303,11 +303,11 @@ def render_content_for(item) # SelectAllColumn # # Renders a "select all" column to the view, yielding the item. - # + # class SelectAllColumn < Column def initialize(opts, &block) - super(opts[:title] || "Select All", opts, &block) + super(opts[:text] || "Select All", opts, &block) end def render_content_for(item) @@ -333,7 +333,7 @@ def render_content_for(item) # render_resource_table_for @users do |t| # t.actions_column do |actions, user| # actions.add 'View', user_path(user) - # actions.add_with_auth title: 'Edit', path: edit_user_path(user), can: :edit, on: user + # actions.add_with_auth text: 'Edit', path: edit_user_path(user), can: :edit, on: user # end # end # diff --git a/app/jobs/get_cloud_assets_job.rb b/app/jobs/get_cloud_assets_job.rb index fdde100c0..1a3fa8360 100644 --- a/app/jobs/get_cloud_assets_job.rb +++ b/app/jobs/get_cloud_assets_job.rb @@ -1,7 +1,7 @@ require 'faraday' # GetCloudAssetsJob retrieves cloud assets from cluster builder such as the -# list of flavors, images and networks available to the given user. +# list of flavors, images and networks available to the given user/team. class GetCloudAssetsJob < ApplicationJob queue_as :default diff --git a/app/models/ability.rb b/app/models/ability.rb index 64c5c561f..00a28d2ef 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -6,10 +6,6 @@ def initialize(user) enable_abilities end - def enough_credits_to_create_cluster? - @user.teams.meets_cluster_credit_requirement.exists? - end - private def enable_abilities @@ -47,8 +43,7 @@ def non_root_abilities can :manage, RackviewPreset, user: @user can :read, ClusterType - can :new, Cluster if enough_credits_to_create_cluster? - can :create, Cluster, team_id: @user.teams.meets_cluster_credit_requirement.pluck(:id) + can :create, Cluster, team_id: @user.team_ids can :read, KeyPair, user: @user can :create, KeyPair, user: @user diff --git a/app/models/cluster.rb b/app/models/cluster.rb index fca8c7f31..8598e7baf 100644 --- a/app/models/cluster.rb +++ b/app/models/cluster.rb @@ -47,9 +47,19 @@ def initialize(cluster_type:, team: nil, name: nil, cluster_params: nil, selecti @team = team @name = name @selections = selections - @field_groups = Cluster::FieldGroups.new(self, cluster_type.field_groups, cluster_type.fields) - @fields = @field_groups.fields - fields.each { |field| field.value = cluster_params[field.id] } if cluster_params + @cluster_params = cluster_params + end + + def field_groups + @field_groups ||= Cluster::FieldGroups.new(self, cluster_type.field_groups, cluster_type.fields) + end + + def fields + return @fields if @fields + + @fields = self.field_groups.fields + @fields.each { |field| field.value = @cluster_params[field.id] } if @cluster_params + @fields end def type_id diff --git a/app/views/cloud_service_configs/_actions.html.erb b/app/views/cloud_service_configs/_actions.html.erb index d52871c0a..7aba0a72c 100644 --- a/app/views/cloud_service_configs/_actions.html.erb +++ b/app/views/cloud_service_configs/_actions.html.erb @@ -1,6 +1,6 @@ <%= render_action_dropdown 'Cloud environment actions' do |builder| - builder.add title: 'View', path: cloud_service_config_path - builder.add title: 'Edit', path: edit_cloud_service_config_path + builder.add text: 'View', path: cloud_service_config_path + builder.add text: 'Edit', path: edit_cloud_service_config_path end %> diff --git a/app/views/cluster_types/_actions.html.erb b/app/views/cluster_types/_actions.html.erb index 52267b51f..c38ad30e5 100644 --- a/app/views/cluster_types/_actions.html.erb +++ b/app/views/cluster_types/_actions.html.erb @@ -2,6 +2,6 @@ <%= render_lhm_actions("Cluster type actions") do |actions| - actions.add title: "Check for latest cluster types", path: cluster_types_path(use_cache: false, team_id: @team) + actions.add text: "Check for latest cluster types", path: cluster_types_path(use_cache: false, team_id: @team) end %> diff --git a/app/views/cluster_types/index.html.erb b/app/views/cluster_types/index.html.erb index 8505ef4b8..4568f056a 100644 --- a/app/views/cluster_types/index.html.erb +++ b/app/views/cluster_types/index.html.erb @@ -1,4 +1,4 @@ -<% set_title "Select cluster type" %> +<% set_title "reate cluster - select cluster type" %> <% content_for(:head) do %> <%= javascript_import_module_tag "cluster_types/index" %> <% end %> diff --git a/app/views/clusters/_actions.html.erb b/app/views/clusters/_actions.html.erb index f96e45e06..a9babea8b 100644 --- a/app/views/clusters/_actions.html.erb +++ b/app/views/clusters/_actions.html.erb @@ -2,7 +2,7 @@ <%= render_lhm_actions("Cluster actions") do |actions| - actions.add title: "Re-select cluster type or team", path: cluster_types_path(team_id: @team) - actions.add title: "Check for cluster type updates", path: new_cluster_type_cluster_path(@cluster_type, use_cache: false, team_id: @team) + actions.add text: "Re-select cluster type or team", path: cluster_types_path(team_id: @team) + actions.add text: "Check for cluster type updates", path: new_cluster_type_cluster_path(@cluster_type, use_cache: false, team_id: @team) end %> diff --git a/app/views/interactive_rack_views/_actions.html.erb b/app/views/interactive_rack_views/_actions.html.erb index eb67e853e..02eea4f5d 100644 --- a/app/views/interactive_rack_views/_actions.html.erb +++ b/app/views/interactive_rack_views/_actions.html.erb @@ -1,6 +1,6 @@ <%= render_action_dropdown 'Rack actions' do |builder| - builder.add title: 'Export data', path: '#', id: :export_link + builder.add text: 'Export data', path: '#', id: :export_link end %> diff --git a/app/views/team_roles/index.html.erb b/app/views/team_roles/index.html.erb index 55d009b75..3d55f3c60 100644 --- a/app/views/team_roles/index.html.erb +++ b/app/views/team_roles/index.html.erb @@ -16,11 +16,11 @@ <% t.attribute_column :user_name %> <% t.attribute_column :role, sortable: true %> <% t.actions_column do |actions, team_role| %> - <% actions.add_with_auth can: :edit, on: team_role, title: 'Edit role', path: edit_team_role_path(team_role) %> + <% actions.add_with_auth can: :edit, on: team_role, text: 'Edit role', path: edit_team_role_path(team_role) %> <% presenter = presenter_for(team_role) %> <% actions.add_with_auth(can: :destroy, on: team_role, - title: 'Remove from team', + text: 'Remove from team', path: team_role_path(team_role), method: 'delete', data: presenter.requires_confirmation?(current_user) ? {confirm: presenter.delete_confirmation(current_user)} : {}, diff --git a/app/views/teams/index.html.erb b/app/views/teams/index.html.erb index 072defce2..0221e0ce7 100644 --- a/app/views/teams/index.html.erb +++ b/app/views/teams/index.html.erb @@ -19,8 +19,8 @@ <% end %> <% end %> <% if current_user.root? %> - <% t.attribute_column :project_id, title: "Project ID", sortable: true %> - <% t.attribute_column :billing_acct_id, title: "Billing Account ID", sortable: true %> + <% t.attribute_column :project_id, text: "Project ID", sortable: true %> + <% t.attribute_column :billing_acct_id, text: "Billing Account ID", sortable: true %> <% end %> <% t.custom_column "Credits", sortable: true, db_column: :credits do |team| %> <% presenter_for(team).formatted_credits %> @@ -33,14 +33,20 @@ <% end %> <% t.actions_column do |actions, team| %> - <% actions.add_with_auth can: :edit, on: team, title: 'Edit', path: edit_team_path(team) %> - <% actions.add_with_auth can: :manage, on: TeamRole.new(team_id: team.id), title: 'Manage Users', path: team_team_roles_path(team) %> - <% actions.add_with_auth can: :read, on: Invoice.new(account: team), title: 'View Invoices', path: team_invoices_path(team) %> - <% actions.add_with_auth can: :create, on: CreditDeposit.new(team: team), title: 'Add Credits', path: new_team_credit_deposit_path(team) %> - <% actions.add_with_auth can: :create, on: Cluster.new(team: team, cluster_type: ClusterType.new), title: 'Create Cluster', path: cluster_types_path(team_id: team.id) %> + <% actions.add_with_auth can: :edit, on: team, text: 'Edit', path: edit_team_path(team) %> + <% actions.add_with_auth can: :manage, on: TeamRole.new(team_id: team.id), text: 'Manage Users', path: team_team_roles_path(team) %> + <% actions.add_with_auth can: :read, on: Invoice.new(account: team), text: 'View Invoices', path: team_invoices_path(team) %> + <% actions.add_with_auth can: :create, on: CreditDeposit.new(team: team), text: 'Add Credits', path: new_team_credit_deposit_path(team) %> + <% if can? :create, Cluster.new(team: team, cluster_type: ClusterType.new) %> + <% if Team.meets_cluster_credit_requirement.where(id: team.id).exists? %> + <% actions.add text: 'Create Cluster', path: cluster_types_path(team_id: team.id) %> + <% else %> + <% actions.add text: 'Create Cluster', path: '#', class: "disabled-action", title: "Team has insufficient credits to create cluster" %> + <% end %> + <% end %> <% actions.add_with_auth(can: :destroy, on: team, - title: 'Delete', + text: 'Delete', path: team_path(team), method: 'delete', data: {confirm: presenter_for(team).delete_confirmation_message}, diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 5ddad2a5a..6dcfa231c 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -10,7 +10,7 @@ <% end %> <% t.attribute_column :id, sortable: true %> - <% t.attribute_column :login, title: "Username", sortable: true do |user, login| %> + <% t.attribute_column :login, text: "Username", sortable: true do |user, login| %> <% user != current_user ? login : "#{login} (you)" %> <% end %> <% t.attribute_column :name, sortable: true %> @@ -21,19 +21,23 @@ <% presenter_for(user).team_role_list %> <% end %> <% t.attribute_column :cloud_user_id, title: "Cloud User ID", sortable: true %> + <% t.custom_column "Teams" do |user| %> + <% presenter_for(user).team_role_list %> + <% end %> + <% t.attribute_column :cloud_user_id, text: "Cloud User ID", sortable: true %> <% t.custom_column "Status", sortable: true, db_column: :deleted_at do |user| %> <% presenter_for(user).status %> <% end %> <% t.actions_column do |actions, user| %> <% if user == current_user %> - <% actions.add title: 'Edit', path: edit_user_registration_path %> + <% actions.add text: 'Edit', path: edit_user_registration_path %> <% else %> - <% actions.add_with_auth can: :edit, on: user, title: 'Edit', path: edit_user_path(user) %> + <% actions.add_with_auth can: :edit, on: user, text: 'Edit', path: edit_user_path(user) %> <% end %> <% actions.add_with_auth(can: :destroy, on: user, - title: 'Delete', + text: 'Delete', path: user_path(user), method: 'delete', data: {confirm: presenter_for(user).delete_confirmation_message}, diff --git a/config/navigation.rb b/config/navigation.rb index 5188c9bee..ced066203 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -27,7 +27,7 @@ if current_user.can?(:read, ClusterType) html_options = {} - if !current_ability.enough_credits_to_create_cluster? + if !current_user.teams.meets_cluster_credit_requirement.exists? html_options[:class] = "limited-action-icon" html_options[:title] = "You must belong to a team with at least #{Rails.application.config.cluster_credit_requirement} credits to create a cluster" end From decc1d11da85c29d0fc215af9500ca3412441e4d Mon Sep 17 00:00:00 2001 From: timalces Date: Tue, 27 Feb 2024 13:26:10 +0000 Subject: [PATCH 10/14] refactored cluster form error cell to allow for errors without inputs --- app/cells/cluster_form_errors/show.erb | 10 ++++++-- app/cells/cluster_form_errors_cell.rb | 33 ++++++++++++++++++++------ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/app/cells/cluster_form_errors/show.erb b/app/cells/cluster_form_errors/show.erb index 7735acb3d..091d6c9a1 100644 --- a/app/cells/cluster_form_errors/show.erb +++ b/app/cells/cluster_form_errors/show.erb @@ -1,5 +1,11 @@ -<% if has_errors? %> +<% if has_errors_without_input_field? %>
    - Please correct the <%= "error".pluralize(error_count) %> below and try again. + Unable to launch cluster: <%= inputless_errors_text %> +
    +<% end %> + +<% if has_input_field_errors? %> +
    + Please correct the <%= "error".pluralize(input_field_error_count) %> below and try again.
    <% end %> diff --git a/app/cells/cluster_form_errors_cell.rb b/app/cells/cluster_form_errors_cell.rb index 0e31ecdd3..27edb742b 100644 --- a/app/cells/cluster_form_errors_cell.rb +++ b/app/cells/cluster_form_errors_cell.rb @@ -6,25 +6,44 @@ def show(cluster) private - def has_errors? - error_count > 0 + def attributes_without_input_field + [:team] end - def error_count + def has_input_field_errors? + input_field_error_count > 0 + end + + def input_field_error_count if @cluster.errors.any? # If the cluster has any errors set against it, it is expected that these # will contain any field errors too. - @cluster.errors.count + @cluster.errors.count - inputless_error_count elsif @cluster.fields.any? { |f| !f.errors.empty? } # If the cluster does not have any errors, it is still possible that the # fields do. These can be set from the cluster builder response. @cluster.fields - .select { |f| !f.errors.empty? } - .map { |f| f.errors.count } - .sum + .select { |f| !f.errors.empty? } + .map { |f| f.errors.count } + .sum else 0 end end + + def has_errors_without_input_field? + @cluster.errors.any? && attributes_without_input_field.any? { |attribute| !@cluster.errors[attribute].empty? } + end + + def inputless_error_count + attributes_without_input_field.select { |attribute| !@cluster.errors[attribute].empty? }.length + end + + def inputless_errors_text + @cluster.errors.select { |error| attributes_without_input_field.include?(error.attribute) } + .map(&:full_message) + .join("; ") + + end end From 317004ec08f61062be7a05a632639baf08ce568f Mon Sep 17 00:00:00 2001 From: timalces Date: Tue, 27 Feb 2024 18:13:40 +0000 Subject: [PATCH 11/14] revert overzealous renaming --- app/cells/resource_table/show.erb | 8 ++++---- app/cells/resource_table_cell.rb | 18 +++++++++--------- app/views/invoices/index.html.erb | 2 +- app/views/teams/index.html.erb | 4 ++-- app/views/users/index.html.erb | 6 +----- 5 files changed, 17 insertions(+), 21 deletions(-) diff --git a/app/cells/resource_table/show.erb b/app/cells/resource_table/show.erb index 8ea939223..079c2474c 100644 --- a/app/cells/resource_table/show.erb +++ b/app/cells/resource_table/show.erb @@ -7,25 +7,25 @@ <% if column.select_all_column? %> - <%= column.text %> + <%= column.title %> <% elsif column.sortable? %> <% column.sortable_header(sort_column, sort_direction) do |sort, direction, current| %> - <%= link_to params.permit!.to_h.merge(sort: sort, direction: direction), text: column.tooltip do %> + <%= link_to params.permit!.to_h.merge(sort: sort, direction: direction), title: column.tooltip do %> <% if current %> <%= tag.span(class: [:current, direction]) %> <% else %> <%= tag.span(class: ['asc-desc']) %> <% end %> - <%= column.text %> + <%= column.title %> <% end %> <% end %> <% else %> - <%= column.text %> + <%= column.title %> <% end %> <% end %> diff --git a/app/cells/resource_table_cell.rb b/app/cells/resource_table_cell.rb index 4f4994f4c..292f637c7 100644 --- a/app/cells/resource_table_cell.rb +++ b/app/cells/resource_table_cell.rb @@ -62,8 +62,8 @@ def attribute_column(method, opts = {}, &block) add_column AttributeColumn.new(method, opts, &block), opts end - def custom_column(text, opts = {}, &block) - add_column CustomColumn.new(text, opts, &block), opts + def custom_column(title, opts = {}, &block) + add_column CustomColumn.new(title, opts, &block), opts end def select_all_column(opts = {}, &block) @@ -186,10 +186,10 @@ def establish_if_paginatable class Column include ActionView::Helpers::TagHelper - attr_reader :text, :tooltip + attr_reader :title, :tooltip - def initialize(text, opts = {}, &block) - @text = opts.delete(:text) || text + def initialize(title, opts = {}, &block) + @title = opts.delete(:title) || title @tooltip = opts.delete(:tooltip) @html_class = opts.delete(:class) @opts = opts @@ -259,8 +259,8 @@ def select_all_column? # class CustomColumn < Column - def initialize(text, opts, &block) - super(text.to_s, opts, &block) + def initialize(title, opts, &block) + super(title.to_s, opts, &block) end def render_content_for(item) @@ -307,7 +307,7 @@ def render_content_for(item) class SelectAllColumn < Column def initialize(opts, &block) - super(opts[:text] || "Select All", opts, &block) + super(opts[:title] || "Select All", opts, &block) end def render_content_for(item) @@ -333,7 +333,7 @@ def render_content_for(item) # render_resource_table_for @users do |t| # t.actions_column do |actions, user| # actions.add 'View', user_path(user) - # actions.add_with_auth text: 'Edit', path: edit_user_path(user), can: :edit, on: user + # actions.add_with_auth title: 'Edit', path: edit_user_path(user), can: :edit, on: user # end # end # diff --git a/app/views/invoices/index.html.erb b/app/views/invoices/index.html.erb index c38c68cce..986089dfc 100644 --- a/app/views/invoices/index.html.erb +++ b/app/views/invoices/index.html.erb @@ -21,6 +21,6 @@ <% t.custom_column "Amount" do |invoice| invoice.formatted_amount_charged end %> <% t.actions_column do |actions, invoice| %> - <% actions.add_with_auth can: :show, on: invoice, title: 'View invoice', path: team_invoice_path(@team, invoice) %> + <% actions.add_with_auth can: :show, on: invoice, text: 'View invoice', path: team_invoice_path(@team, invoice) %> <% end %> <% end %> diff --git a/app/views/teams/index.html.erb b/app/views/teams/index.html.erb index 0221e0ce7..50cd255ef 100644 --- a/app/views/teams/index.html.erb +++ b/app/views/teams/index.html.erb @@ -19,8 +19,8 @@ <% end %> <% end %> <% if current_user.root? %> - <% t.attribute_column :project_id, text: "Project ID", sortable: true %> - <% t.attribute_column :billing_acct_id, text: "Billing Account ID", sortable: true %> + <% t.attribute_column :project_id, title: "Project ID", sortable: true %> + <% t.attribute_column :billing_acct_id, title: "Billing Account ID", sortable: true %> <% end %> <% t.custom_column "Credits", sortable: true, db_column: :credits do |team| %> <% presenter_for(team).formatted_credits %> diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 6dcfa231c..44ee3d504 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -10,7 +10,7 @@ <% end %> <% t.attribute_column :id, sortable: true %> - <% t.attribute_column :login, text: "Username", sortable: true do |user, login| %> + <% t.attribute_column :login, title: "Username", sortable: true do |user, login| %> <% user != current_user ? login : "#{login} (you)" %> <% end %> <% t.attribute_column :name, sortable: true %> @@ -21,10 +21,6 @@ <% presenter_for(user).team_role_list %> <% end %> <% t.attribute_column :cloud_user_id, title: "Cloud User ID", sortable: true %> - <% t.custom_column "Teams" do |user| %> - <% presenter_for(user).team_role_list %> - <% end %> - <% t.attribute_column :cloud_user_id, text: "Cloud User ID", sortable: true %> <% t.custom_column "Status", sortable: true, db_column: :deleted_at do |user| %> <% presenter_for(user).status %> <% end %> From bf9dc039f84a5a7df0ebb500514844f17a2f4675 Mon Sep 17 00:00:00 2001 From: timalces Date: Tue, 27 Feb 2024 18:38:11 +0000 Subject: [PATCH 12/14] updated tests --- app/models/cluster.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/cluster.rb b/app/models/cluster.rb index 8598e7baf..1fddfde33 100644 --- a/app/models/cluster.rb +++ b/app/models/cluster.rb @@ -98,6 +98,8 @@ def add_field_error(field_or_id, error) #################################### def valid_fields? + return unless cluster_type + fields.each do |field| unless field.valid? errors.add(field.label, field.errors.messages_for(:value).join("; ")) From 7bbcc61631aece98b0e100d0a7a2abb750220212 Mon Sep 17 00:00:00 2001 From: timalces Date: Mon, 4 Mar 2024 11:20:37 +0000 Subject: [PATCH 13/14] removed obsolete accessors --- app/models/cluster.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/models/cluster.rb b/app/models/cluster.rb index 1fddfde33..a7f5e4460 100644 --- a/app/models/cluster.rb +++ b/app/models/cluster.rb @@ -10,8 +10,6 @@ class Cluster attr_accessor :cluster_type attr_accessor :team attr_accessor :name - attr_accessor :fields - attr_accessor :field_groups attr_reader :selections #################################### From fc7c5ce2be00d5fa0ab409f4fda2748c76720129 Mon Sep 17 00:00:00 2001 From: timalces Date: Mon, 4 Mar 2024 15:26:50 +0000 Subject: [PATCH 14/14] fixed typo --- app/views/cluster_types/index.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/cluster_types/index.html.erb b/app/views/cluster_types/index.html.erb index 4568f056a..af45e7bea 100644 --- a/app/views/cluster_types/index.html.erb +++ b/app/views/cluster_types/index.html.erb @@ -1,4 +1,4 @@ -<% set_title "reate cluster - select cluster type" %> +<% set_title "Create cluster - select cluster type" %> <% content_for(:head) do %> <%= javascript_import_module_tag "cluster_types/index" %> <% end %>