diff --git a/Gemfile b/Gemfile index 2e1e3ec14..f18305970 100644 --- a/Gemfile +++ b/Gemfile @@ -181,6 +181,9 @@ gem 'reverse_markdown' group :production do # we dont want request timing out in development while using byebug gem 'rack-timeout' + # for heroku autoscaling + gem 'judoscale-rails', require: false + gem 'judoscale-sidekiq', require: false end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 41140bdda..e2f5c91be 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -397,6 +397,13 @@ GEM hana (~> 1.3) regexp_parser (~> 2.0) uri_template (~> 0.7) + judoscale-rails (1.8.2) + judoscale-ruby (= 1.8.2) + railties + judoscale-ruby (1.8.2) + judoscale-sidekiq (1.8.2) + judoscale-ruby (= 1.8.2) + sidekiq (>= 5.0) jwt (2.8.1) base64 kaminari (1.2.2) @@ -891,6 +898,8 @@ DEPENDENCIES jbuilder json_refs json_schemer + judoscale-rails + judoscale-sidekiq jwt kaminari koala diff --git a/app/controllers/api/v1/accounts/integrations/captain_controller.rb b/app/controllers/api/v1/accounts/integrations/captain_controller.rb index 7df0e61df..15e81b726 100644 --- a/app/controllers/api/v1/accounts/integrations/captain_controller.rb +++ b/app/controllers/api/v1/accounts/integrations/captain_controller.rb @@ -1,22 +1,53 @@ class Api::V1::Accounts::Integrations::CaptainController < Api::V1::Accounts::BaseController - before_action :check_admin_authorization? - before_action :fetch_hook + before_action :hook - def sso_url - params_string = - "token=#{URI.encode_www_form_component(@hook['settings']['access_token'])}" \ - "&email=#{URI.encode_www_form_component(@hook['settings']['account_email'])}" \ - "&account_id=#{URI.encode_www_form_component(@hook['settings']['account_id'])}" - - installation_config = InstallationConfig.find_by(name: 'CAPTAIN_APP_URL') - - sso_url = "#{installation_config.value}/sso?#{params_string}" - render json: { sso_url: sso_url }, status: :ok + def proxy + response = HTTParty.send(request_method, request_url, body: permitted_params[:body].to_json, headers: headers) + render plain: response.body, status: response.code end private - def fetch_hook - @hook = Current.account.hooks.find_by!(app_id: 'captain') + def headers + { + 'X-User-Email' => hook.settings['account_email'], + 'X-User-Token' => hook.settings['access_token'], + 'Content-Type' => 'application/json', + 'Accept' => '*/*' + } + end + + def request_path + request_route = with_leading_hash_on_route(params[:route]) + + return 'api/sessions/profile' if request_route == '/sessions/profile' + + "api/accounts/#{hook.settings['account_id']}#{request_route}" + end + + def request_url + base_url = InstallationConfig.find_by(name: 'CAPTAIN_API_URL').value + URI.join(base_url, request_path).to_s + end + + def hook + @hook ||= Current.account.hooks.find_by!(app_id: 'captain') + end + + def request_method + method = permitted_params[:method].downcase + raise 'Invalid or missing HTTP method' unless %w[get post put patch delete options head].include?(method) + + method + end + + def with_leading_hash_on_route(request_route) + return '' if request_route.blank? + + request_route.start_with?('/') ? request_route : "/#{request_route}" + end + + def permitted_params + params.permit(:method, :route, body: {}) end end diff --git a/app/controllers/platform/api/v1/accounts_controller.rb b/app/controllers/platform/api/v1/accounts_controller.rb index 9e6d53fef..e11cf9d4a 100644 --- a/app/controllers/platform/api/v1/accounts_controller.rb +++ b/app/controllers/platform/api/v1/accounts_controller.rb @@ -4,6 +4,7 @@ class Platform::Api::V1::AccountsController < PlatformController def create @resource = Account.create!(account_params) update_resource_features + @resource.save! @platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource) end diff --git a/app/javascript/dashboard/api/integrations.js b/app/javascript/dashboard/api/integrations.js index 975857ce1..59a3e244a 100644 --- a/app/javascript/dashboard/api/integrations.js +++ b/app/javascript/dashboard/api/integrations.js @@ -33,8 +33,8 @@ class IntegrationsAPI extends ApiClient { return axios.delete(`${this.baseUrl()}/integrations/hooks/${hookId}`); } - fetchCaptainURL() { - return axios.get(`${this.baseUrl()}/integrations/captain/sso_url`); + requestCaptain(body) { + return axios.post(`${this.baseUrl()}/integrations/captain/proxy`, body); } } diff --git a/app/javascript/dashboard/assets/scss/widgets/_base.scss b/app/javascript/dashboard/assets/scss/widgets/_base.scss index d5d2e227f..d35c1cfc9 100644 --- a/app/javascript/dashboard/assets/scss/widgets/_base.scss +++ b/app/javascript/dashboard/assets/scss/widgets/_base.scss @@ -31,7 +31,13 @@ hr { ul, ol, dl { - @apply mb-2 list-disc list-outside leading-[1.65]; + @apply list-disc list-outside leading-[1.65]; +} + +ul:not(.reset-base), +ol:not(.reset-base), +dl:not(.reset-base) { + @apply mb-0; } // Form elements diff --git a/app/javascript/dashboard/assets/scss/widgets/_buttons.scss b/app/javascript/dashboard/assets/scss/widgets/_buttons.scss index 587b810ca..640e1395f 100644 --- a/app/javascript/dashboard/assets/scss/widgets/_buttons.scss +++ b/app/javascript/dashboard/assets/scss/widgets/_buttons.scss @@ -96,7 +96,7 @@ button { } // @TODDO - Remove after moving all buttons to woot-button - .icon+.button__content { + .icon + .button__content { @apply w-auto; } diff --git a/app/javascript/dashboard/components-next/Campaigns/CampaignCard/CampaignCard.vue b/app/javascript/dashboard/components-next/Campaigns/CampaignCard/CampaignCard.vue index dbdbb5b45..229553253 100644 --- a/app/javascript/dashboard/components-next/Campaigns/CampaignCard/CampaignCard.vue +++ b/app/javascript/dashboard/components-next/Campaigns/CampaignCard/CampaignCard.vue @@ -82,60 +82,56 @@ const inboxIcon = computed(() => { diff --git a/app/javascript/dashboard/components-next/Campaigns/CampaignCard/LiveChatCampaignDetails.vue b/app/javascript/dashboard/components-next/Campaigns/CampaignCard/LiveChatCampaignDetails.vue index 6f8c548d4..6a883ce6b 100644 --- a/app/javascript/dashboard/components-next/Campaigns/CampaignCard/LiveChatCampaignDetails.vue +++ b/app/javascript/dashboard/components-next/Campaigns/CampaignCard/LiveChatCampaignDetails.vue @@ -1,7 +1,8 @@ - + diff --git a/app/javascript/dashboard/components-next/avatar/EditableAvatar.story.vue b/app/javascript/dashboard/components-next/avatar/EditableAvatar.story.vue deleted file mode 100644 index ca0e38173..000000000 --- a/app/javascript/dashboard/components-next/avatar/EditableAvatar.story.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - diff --git a/app/javascript/dashboard/components-next/avatar/EditableAvatar.vue b/app/javascript/dashboard/components-next/avatar/EditableAvatar.vue deleted file mode 100644 index 2c71963c1..000000000 --- a/app/javascript/dashboard/components-next/avatar/EditableAvatar.vue +++ /dev/null @@ -1,105 +0,0 @@ - - - diff --git a/app/javascript/dashboard/components-next/button/Button.vue b/app/javascript/dashboard/components-next/button/Button.vue index 67918be2e..02dcfad53 100644 --- a/app/javascript/dashboard/components-next/button/Button.vue +++ b/app/javascript/dashboard/components-next/button/Button.vue @@ -6,7 +6,7 @@ import Icon from 'dashboard/components-next/icon/Icon.vue'; const props = defineProps({ label: { - type: String, + type: [String, Number], default: '', }, variant: { @@ -47,9 +47,9 @@ const STYLE_CONFIG = { blue: { solid: 'bg-n-brand text-white hover:brightness-110 outline-transparent', faded: - 'bg-n-brand/10 text-n-slate-12 hover:bg-n-brand/20 outline-transparent', + 'bg-n-brand/10 text-n-blue-text hover:bg-n-brand/20 outline-transparent', outline: 'text-n-blue-text outline-n-blue-border', - link: 'text-n-brand hover:underline outline-transparent', + link: 'text-n-blue-text hover:underline outline-transparent', }, ruby: { solid: 'bg-n-ruby-9 text-white hover:bg-n-ruby-10 outline-transparent', @@ -161,7 +161,7 @@ const linkButtonClasses = computed(() => { - {{ label }} + {{ label }} diff --git a/app/javascript/dashboard/components-next/combobox/ComboBox.vue b/app/javascript/dashboard/components-next/combobox/ComboBox.vue index 7fce6f64c..de84974e6 100644 --- a/app/javascript/dashboard/components-next/combobox/ComboBox.vue +++ b/app/javascript/dashboard/components-next/combobox/ComboBox.vue @@ -43,7 +43,7 @@ const props = defineProps({ }, }); -const emit = defineEmits(['update:modelValue']); +const emit = defineEmits(['update:modelValue', 'search']); const { t } = useI18n(); @@ -118,13 +118,13 @@ watch( diff --git a/app/javascript/dashboard/components-next/combobox/ComboBoxDropdown.vue b/app/javascript/dashboard/components-next/combobox/ComboBoxDropdown.vue index 4a0e3856c..a39657624 100644 --- a/app/javascript/dashboard/components-next/combobox/ComboBoxDropdown.vue +++ b/app/javascript/dashboard/components-next/combobox/ComboBoxDropdown.vue @@ -11,10 +11,6 @@ const props = defineProps({ type: Array, required: true, }, - searchValue: { - type: String, - required: true, - }, searchPlaceholder: { type: String, default: '', @@ -33,10 +29,15 @@ const props = defineProps({ }, }); -const emit = defineEmits(['update:searchValue', 'select']); +const emit = defineEmits(['update:searchValue', 'select', 'search']); const { t } = useI18n(); +const searchValue = defineModel('searchValue', { + type: String, + default: '', +}); + const searchInput = ref(null); const isSelected = option => { @@ -46,6 +47,11 @@ const isSelected = option => { return option.value === props.selectedValues; }; +const onInputSearch = event => { + searchValue.value = event.target.value; + emit('search', event.target.value); +}; + defineExpose({ focus: () => searchInput.value?.focus(), }); @@ -64,7 +70,7 @@ defineExpose({ type="search" :placeholder="searchPlaceholder || t('COMBOBOX.SEARCH_PLACEHOLDER')" class="w-full py-2 pl-10 pr-2 text-sm border-none rounded-t-md bg-n-solid-1 text-slate-900 dark:text-slate-50" - @input="emit('update:searchValue', $event.target.value)" + @input="onInputSearch" />