From 622de7701a9436926f0636549899b29ceb954042 Mon Sep 17 00:00:00 2001 From: Jason Fine Date: Tue, 9 Apr 2024 13:31:22 -0400 Subject: [PATCH 01/10] squash commits --- .gitignore | 4 ++- Gemfile | 3 ++ Gemfile.lock | 4 +++ app/assets/javascripts/fae/form/_validator.js | 4 +++ app/controllers/fae/deploy_controller.rb | 1 - .../fae/netlify_hooks_controller.rb | 35 +++++++++++++++++++ app/mailers/fae/deploy_notifications.rb | 17 +++++++++ app/models/fae/deploy.rb | 5 +++ .../notify_admins.text.erb | 15 ++++++++ app/views/fae/users/_form.html.slim | 1 + app/views/layouts/fae/mailer.text.slim | 1 + config/initializers/devise.rb | 5 +++ config/locales/fae.en.yml | 1 + config/routes.rb | 2 ++ ...ceive_deploy_notifications_to_fae_users.rb | 6 ++++ .../20240222165620_create_fae_deploys.rb | 13 +++++++ docker-compose.yml | 6 ++-- docs/features/netlify.md | 5 ++- docs/topics/initializer.md | 1 + fae.gemspec | 1 + lib/fae/engine.rb | 3 +- lib/fae/options.rb | 35 ++++++++++--------- .../fae/templates/initializers/fae.rb | 8 ++++- .../fae/templates/initializers/fae_fine.rb | 5 +++ spec/dummy/app/assets/javascripts/fae.js | 1 + spec/dummy/app/mailers/.keep | 0 spec/dummy/config/environments/development.rb | 2 ++ spec/dummy/config/initializers/fae.rb | 11 +++--- ...e_deploy_notifications_to_fae_users.fae.rb | 7 ++++ .../20240222170611_create_fae_deploys.fae.rb | 14 ++++++++ spec/dummy/db/schema.rb | 24 ++++++++++--- 31 files changed, 206 insertions(+), 34 deletions(-) create mode 100644 app/controllers/fae/netlify_hooks_controller.rb create mode 100644 app/mailers/fae/deploy_notifications.rb create mode 100644 app/models/fae/deploy.rb create mode 100644 app/views/fae/deploy_notifications/notify_admins.text.erb create mode 100644 app/views/layouts/fae/mailer.text.slim create mode 100644 db/migrate/20240220144206_add_receive_deploy_notifications_to_fae_users.rb create mode 100644 db/migrate/20240222165620_create_fae_deploys.rb delete mode 100644 spec/dummy/app/mailers/.keep create mode 100644 spec/dummy/db/migrate/20240220152521_add_receive_deploy_notifications_to_fae_users.fae.rb create mode 100644 spec/dummy/db/migrate/20240222170611_create_fae_deploys.fae.rb diff --git a/.gitignore b/.gitignore index 37f9388f6..18298e21b 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,6 @@ spec/dummy/public/uploads/* /vendor docker-compose.override.yml -.env \ No newline at end of file +.env +mysql +node_modules \ No newline at end of file diff --git a/Gemfile b/Gemfile index df4cf536e..6b1d72440 100644 --- a/Gemfile +++ b/Gemfile @@ -52,3 +52,6 @@ gem "puma", "~> 5.0" gem "fog-aws" gem 'ddtrace', require: 'ddtrace/auto_instrument' + +# For emailing +# gem 'aws-sdk-rails', '~> 3.1.0' diff --git a/Gemfile.lock b/Gemfile.lock index 8e7d6eef3..c5865b5af 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -27,6 +27,7 @@ PATH jquery-ui-rails (~> 6.0.1) judge (~> 3.1.0) judge-simple_form (~> 1.1.0) + jwt (~> 2.8.0) kaminari mini_magick rails (>= 5.0) @@ -115,6 +116,7 @@ GEM bundler rake thor (>= 0.14.0) + base64 (0.2.0) bcrypt (3.1.16) better_errors (2.9.1) coderay (>= 1.0.0) @@ -240,6 +242,8 @@ GEM judge-simple_form (1.1.0) judge (>= 2.0) simple_form (>= 3.0) + jwt (2.8.0) + base64 kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) diff --git a/app/assets/javascripts/fae/form/_validator.js b/app/assets/javascripts/fae/form/_validator.js index 2de56a12c..b064c71aa 100644 --- a/app/assets/javascripts/fae/form/_validator.js +++ b/app/assets/javascripts/fae/form/_validator.js @@ -464,6 +464,10 @@ Fae.form.validator = { ); _this.addCustomValidation(); } + // See https://github.com/wearefine/fae/issues/409 + // This was preventing the functionality of a checkbox field + // on the user form. + _this.$password_field.data('validate', ''); }, /** diff --git a/app/controllers/fae/deploy_controller.rb b/app/controllers/fae/deploy_controller.rb index 33e448c8a..f33027e8f 100644 --- a/app/controllers/fae/deploy_controller.rb +++ b/app/controllers/fae/deploy_controller.rb @@ -19,6 +19,5 @@ def deploy_site end render json: {success: false} end - end end diff --git a/app/controllers/fae/netlify_hooks_controller.rb b/app/controllers/fae/netlify_hooks_controller.rb new file mode 100644 index 000000000..0594c55f2 --- /dev/null +++ b/app/controllers/fae/netlify_hooks_controller.rb @@ -0,0 +1,35 @@ +module Fae + class NetlifyHooksController < ActionController::Base + + def netlify_hook + signature = request.headers["X-Webhook-Signature"] + if signature.blank? + Rails.logger.info 'request.headers["X-Webhook-Signature"] header is missing' + return head :forbidden + end + if Fae.netlify[:notification_hook_signature].blank? + Rails.logger.info "Fae.netlify[:notification_hook_signature] is not set" + return head :forbidden + end + + options = {iss: "netlify", verify_iss: true, algorithm: "HS256"} + decoded = JWT.decode(signature, Fae.netlify[:notification_hook_signature], true, options) + unless decoded.first['sha256'] == Digest::SHA256.hexdigest(request.body.read) + Rails.logger.info "Netlify hook signature mismatch, check the value of Fae.netlify[:notification_hook_signature] against the value of the JWS secret token in the Netlify webhook settings." + return head :forbidden + end + + body = JSON.parse(request.body.read) + + # Don't notify if the deploy is a code push + if body['commit_ref'].present? + Rails.logger.info "#{body['context']} - #{body['id']} is a code push, skipping notification" + Rails.logger.info "#{body['branch']} #{body['commit_ref']}: #{body['commit_message']}" + return head :ok + end + + DeployNotifications.notify_admins(body).deliver_now + return head :ok + end + end +end diff --git a/app/mailers/fae/deploy_notifications.rb b/app/mailers/fae/deploy_notifications.rb new file mode 100644 index 000000000..b01272299 --- /dev/null +++ b/app/mailers/fae/deploy_notifications.rb @@ -0,0 +1,17 @@ +module Fae + class DeployNotifications < ActionMailer::Base + default from: Fae.deploy_notifications_mailer_sender + layout 'layouts/fae/mailer' + + def notify_admins(body = nil, additional_emails = []) + Rails.logger.info 'Sending deploy notification' + @deploy = body + recipients = Fae::User.where(receive_deploy_notifications: true).pluck(:email) + recipients += additional_emails + @fae_options = Fae::Option.instance + current_time_in_zone = Time.now.in_time_zone(@fae_options.time_zone).strftime('%Y-%m-%d %l:%M %p') + subject = "#{@fae_options.title} Deploy Notification #{current_time_in_zone}" + mail(to: recipients, subject: subject) + end + end +end \ No newline at end of file diff --git a/app/models/fae/deploy.rb b/app/models/fae/deploy.rb new file mode 100644 index 000000000..57457abb1 --- /dev/null +++ b/app/models/fae/deploy.rb @@ -0,0 +1,5 @@ +module Fae + class Deploy < ApplicationRecord + belongs_to :user + end +end \ No newline at end of file diff --git a/app/views/fae/deploy_notifications/notify_admins.text.erb b/app/views/fae/deploy_notifications/notify_admins.text.erb new file mode 100644 index 000000000..ff14025b2 --- /dev/null +++ b/app/views/fae/deploy_notifications/notify_admins.text.erb @@ -0,0 +1,15 @@ +<% if @deploy['branch'] == 'master' || @deploy['branch'] == 'main' %> + A deploy to the production environment has completed. + <%= @fae_options.live_url %> +<% elsif @deploy['branch'] == 'staging' %> + A deploy to the Staging environment has completed. + <%= @fae_options.stage_url %> +<% else %> + A deploy to the <%= @deploy['branch'] %> environment has completed. +<% end %> + +<% if @deploy['state'] == 'ready' %> + The deploy was successful. +<% else %> + An error occurred. Please contact your FINE team. +<% end %> \ No newline at end of file diff --git a/app/views/fae/users/_form.html.slim b/app/views/fae/users/_form.html.slim index 69253abc0..f940b5a42 100644 --- a/app/views/fae/users/_form.html.slim +++ b/app/views/fae/users/_form.html.slim @@ -9,6 +9,7 @@ = fae_input f, :first_name = fae_input f, :last_name = fae_input f, :email + = fae_input f, :receive_deploy_notifications, helper_text: t('fae.user.receive_deploy_notifications_hint') = fae_input f, :password, helper_text: t('fae.user.password_hint') = fae_input f, :password_confirmation - if current_user.admin? || current_user.super_admin? diff --git a/app/views/layouts/fae/mailer.text.slim b/app/views/layouts/fae/mailer.text.slim new file mode 100644 index 000000000..f1d0cc898 --- /dev/null +++ b/app/views/layouts/fae/mailer.text.slim @@ -0,0 +1 @@ += yield \ No newline at end of file diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 91243cd0d..c39027119 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -15,6 +15,11 @@ # Configure the class responsible to send e-mails. # config.mailer = 'Devise::Mailer' + # Configure the e-mail address which will be used for deploy notifications, + # note that it will be overwritten if you use your own mailer class + # with default "from" parameter. + # config.deploy_notifications_mailer_sender = 'change-me@example.com' + # ==> ORM configuration # Load and configure the ORM. Supports :active_record (default) and # :mongoid (bson_ext recommended) by default. Other ORMs may be diff --git a/config/locales/fae.en.yml b/config/locales/fae.en.yml index 2c2f71d93..16d44d1d1 100644 --- a/config/locales/fae.en.yml +++ b/config/locales/fae.en.yml @@ -30,6 +30,7 @@ en: edit: 'Edit' user: password_hint: 'To update your password, fill out the fields below. Otherwise leave blank. Passwords must contain at least 8 characters.' + receive_deploy_notifications_hint: 'Check to receive deploy notifications via email.' header: 'Your Settings' last_login: 'Last Logged In' active: 'Active' diff --git a/config/routes.rb b/config/routes.rb index f44dbae82..d594a30a8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -45,6 +45,8 @@ get 'deploy/deploys_list' => 'deploy#deploys_list', as: 'deploy_deploys_list' post 'deploy/deploy_site' => 'deploy#deploy_site', as: 'deploy_deploy_site' + + post 'netlify_hooks/netlify_hook' => 'netlify_hooks#netlify_hook' ## catch all 404 match "*path" => 'pages#error404', via: [:get, :post] diff --git a/db/migrate/20240220144206_add_receive_deploy_notifications_to_fae_users.rb b/db/migrate/20240220144206_add_receive_deploy_notifications_to_fae_users.rb new file mode 100644 index 000000000..ae76c7342 --- /dev/null +++ b/db/migrate/20240220144206_add_receive_deploy_notifications_to_fae_users.rb @@ -0,0 +1,6 @@ +class AddReceiveDeployNotificationsToFaeUsers < ActiveRecord::Migration[7.0] + def change + add_column :fae_users, :receive_deploy_notifications, :boolean, default: false + add_index :fae_users, :receive_deploy_notifications + end +end diff --git a/db/migrate/20240222165620_create_fae_deploys.rb b/db/migrate/20240222165620_create_fae_deploys.rb new file mode 100644 index 000000000..01d34d59d --- /dev/null +++ b/db/migrate/20240222165620_create_fae_deploys.rb @@ -0,0 +1,13 @@ +class CreateFaeDeploys < ActiveRecord::Migration[7.0] + def change + create_table :fae_deploys do |t| + t.integer :user_id, index: true + t.string :environment + t.string :deploy_id, index: true + t.string :deploy_status + t.boolean :notified + + t.timestamps + end + end +end diff --git a/docker-compose.yml b/docker-compose.yml index fd4a60d9b..0dbb70a5e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,8 +20,8 @@ environment: - RAILS_ENV=development - DEV_MYSQL_HOST=db - - FINE_NETLIFY_API_TOKEN=test - - FINE_NETLIFY_API_USER=test + - FINE_NETLIFY_API_TOKEN=abc123 + - FINE_NETLIFY_API_USER=user@email.com db: # image: postgres # volumes: @@ -37,4 +37,4 @@ ports: - '3307:3306' volumes: - - /private/app/dbs:/var/lib/mysql + - ./mysql:/var/lib/mysql diff --git a/docs/features/netlify.md b/docs/features/netlify.md index db420a99a..7489f8708 100644 --- a/docs/features/netlify.md +++ b/docs/features/netlify.md @@ -15,6 +15,8 @@ The deploy page will also display the status of the current deploy, along with a To enable this feature, make sure `config.netlify` is defined in your Fae initializer with all options set correctly. +`notification_hook_signature` is only required if you will use incoming Netlify deploy notification webhooks. + `config/initializers/fae.rb` ```ruby Fae.setup do |config| @@ -26,7 +28,8 @@ Fae.setup do |config| api_token: 'netlify-api-token', site: 'site-name-in-netlify', site_id: 'site-id-in-netlify', - api_base: 'https://api.netlify.com/api/v1/' + api_base: 'https://api.netlify.com/api/v1/', + notification_hook_signature: 'netlify-notification-hook-signature' } end diff --git a/docs/topics/initializer.md b/docs/topics/initializer.md index 418d4fc26..b0750853c 100644 --- a/docs/topics/initializer.md +++ b/docs/topics/initializer.md @@ -6,6 +6,7 @@ Fae's default config can be overwritten in a `config/initializers/fae.rb` file. | --- | ---- | ------- | ----------- | | devise_secret_key | string | | unique Devise hash, generated on `rails g fae:install` | | devise_mailer_sender | string | "change-me@example.com" | This email address will get passed to Devise and used as the from address in the password reset emails. | +| deploy_notifications_mailer_sender | string | "change-me@example.com" | This email address will get passed to Fae and used as the from address in the deploy notification emails. | | dashboard_exclusions | array | [] | The dashboard will show all objects with recent activity. | | max_image_upload_size | integer | 2 | This will set a file size limit on image uploads in MBs. | | max_file_upload_size | integer | 5 | This will set a file size limit on file uploads in MBs. | diff --git a/fae.gemspec b/fae.gemspec index 9d07dfcca..88e0a74db 100644 --- a/fae.gemspec +++ b/fae.gemspec @@ -57,6 +57,7 @@ Gem::Specification.new do |s| s.add_dependency 'remotipart', '~> 1.4.0' s.add_dependency 'simple_form', '<= 5.1' s.add_dependency 'slim' + s.add_dependency 'jwt', '~> 2.8.0' s.add_development_dependency 'appraisal' s.add_development_dependency 'better_errors' diff --git a/lib/fae/engine.rb b/lib/fae/engine.rb index 0a349ac4f..9c1296103 100644 --- a/lib/fae/engine.rb +++ b/lib/fae/engine.rb @@ -14,7 +14,8 @@ class Engine < ::Rails::Engine require 'slim' require 'kaminari' require 'fae/version' - require "sprockets/railtie" + require 'sprockets/railtie' + require 'jwt' config.eager_load_paths += %W(#{config.root}/app) diff --git a/lib/fae/options.rb b/lib/fae/options.rb index b1dacd252..e4dfdded0 100644 --- a/lib/fae/options.rb +++ b/lib/fae/options.rb @@ -4,24 +4,25 @@ module Fae # configurable defaults - mattr_accessor :devise_secret_key, :devise_mailer_sender, :dashboard_exclusions, :max_image_upload_size, :max_file_upload_size, :languages, :recreate_versions, :validation_helpers, :track_changes, :tracker_history_length, :slug_separator, :disabled_environments, :per_page, :use_cache, :use_form_manager, :netlify + mattr_accessor :devise_secret_key, :devise_mailer_sender, :deploy_notifications_mailer_sender, :dashboard_exclusions, :max_image_upload_size, :max_file_upload_size, :languages, :recreate_versions, :validation_helpers, :track_changes, :tracker_history_length, :slug_separator, :disabled_environments, :per_page, :use_cache, :use_form_manager, :netlify - self.devise_secret_key = '' - self.devise_mailer_sender = 'change-me@example.com' - self.dashboard_exclusions = [] - self.max_image_upload_size = 2 - self.max_file_upload_size = 5 - self.languages = {} - self.recreate_versions = false - self.validation_helpers = ValidationHelperCollection.new - self.track_changes = true - self.tracker_history_length = 15 - self.slug_separator = '-' - self.disabled_environments = [] - self.per_page = 25 - self.use_cache = false - self.use_form_manager = false - self.netlify = {} + self.devise_secret_key = '' + self.devise_mailer_sender = 'change-me@example.com' + self.deploy_notifications_mailer_sender = 'change-me@example.com' + self.dashboard_exclusions = [] + self.max_image_upload_size = 2 + self.max_file_upload_size = 5 + self.languages = {} + self.recreate_versions = false + self.validation_helpers = ValidationHelperCollection.new + self.track_changes = true + self.tracker_history_length = 15 + self.slug_separator = '-' + self.disabled_environments = [] + self.per_page = 25 + self.use_cache = false + self.use_form_manager = false + self.netlify = {} # this function maps the vars from your app into your engine def self.setup(&block) diff --git a/lib/generators/fae/templates/initializers/fae.rb b/lib/generators/fae/templates/initializers/fae.rb index 931294e58..39f96a733 100644 --- a/lib/generators/fae/templates/initializers/fae.rb +++ b/lib/generators/fae/templates/initializers/fae.rb @@ -1,5 +1,10 @@ Fae.setup do |config| + ## deploy_notifications_mailer_sender + # This email address will get passed to Fae and + # used as the from address in the deploy notification emails. + # config.deploy_notifications_mailer_sender = 'change-me@example.com' + ## devise_mailer_sender # This email address will get passed to Devise and # used as the from address in the password reset emails. @@ -73,6 +78,7 @@ # api_token: 'netlify-api-token', # site: 'site-name-in-netlify', # site_id: 'site-id-in-netlify', - # api_base: 'https://api.netlify.com/api/v1/' + # api_base: 'https://api.netlify.com/api/v1/', + # notification_hook_signature: 'netlify-notification-hook-signature' # } end \ No newline at end of file diff --git a/lib/generators/fae/templates/initializers/fae_fine.rb b/lib/generators/fae/templates/initializers/fae_fine.rb index 71d071686..25df3dc8b 100644 --- a/lib/generators/fae/templates/initializers/fae_fine.rb +++ b/lib/generators/fae/templates/initializers/fae_fine.rb @@ -1,5 +1,10 @@ Fae.setup do |config| + ## deploy_notifications_mailer_sender + # This email address will get passed to Fae and + # used as the from address in the deploy notification emails. + # config.deploy_notifications_mailer_sender = 'change-me@example.com' + ## devise_mailer_sender # This email address will get passed to Devise and # used as the from address in the password reset emails. diff --git a/spec/dummy/app/assets/javascripts/fae.js b/spec/dummy/app/assets/javascripts/fae.js index 913348c5f..b4c7d369c 100644 --- a/spec/dummy/app/assets/javascripts/fae.js +++ b/spec/dummy/app/assets/javascripts/fae.js @@ -6,6 +6,7 @@ //= require fae/vendor/trumbowyg $(document).ready(function(){ + $('.login-body').addClass('test-class'); $("body").on("modal:show", function (e) { diff --git a/spec/dummy/app/mailers/.keep b/spec/dummy/app/mailers/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/spec/dummy/config/environments/development.rb b/spec/dummy/config/environments/development.rb index afcfaed4f..ce72531c7 100644 --- a/spec/dummy/config/environments/development.rb +++ b/spec/dummy/config/environments/development.rb @@ -36,4 +36,6 @@ # config.action_view.raise_on_missing_translations = true config.action_mailer.default_url_options = { :host => 'localhost' } + + config.hosts << "e94c-47-26-150-197.ngrok-free.app" end diff --git a/spec/dummy/config/initializers/fae.rb b/spec/dummy/config/initializers/fae.rb index 0937ca9c5..9993ed4ac 100644 --- a/spec/dummy/config/initializers/fae.rb +++ b/spec/dummy/config/initializers/fae.rb @@ -4,6 +4,8 @@ config.devise_mailer_sender = 'test@test.com' + config.deploy_notifications_mailer_sender = 'test@test.com' + config.max_image_upload_size = 1 # models to exclude from dashboard list @@ -22,13 +24,14 @@ config.use_form_manager = true # Removed for now to simplify render.com deploy - if Rails.env.test? + if Rails.env.test? || Rails.env.development? config.netlify = { api_user: ENV['FINE_NETLIFY_API_USER'], api_token: ENV['FINE_NETLIFY_API_TOKEN'], - site: 'fine-pss', - site_id: 'bb32173b-9ff2-4d9d-860a-2683ae4e1e2b', - api_base: 'https://api.netlify.com/api/v1/' + site: 'nuxt3-pss-deployment-refactor', + site_id: '6b73c3a4-dd29-4acf-b65a-c9745b0f08eb', + api_base: 'https://api.netlify.com/api/v1/', + notification_hook_signature: '123test' } end end diff --git a/spec/dummy/db/migrate/20240220152521_add_receive_deploy_notifications_to_fae_users.fae.rb b/spec/dummy/db/migrate/20240220152521_add_receive_deploy_notifications_to_fae_users.fae.rb new file mode 100644 index 000000000..3645193cb --- /dev/null +++ b/spec/dummy/db/migrate/20240220152521_add_receive_deploy_notifications_to_fae_users.fae.rb @@ -0,0 +1,7 @@ +# This migration comes from fae (originally 20240220144206) +class AddReceiveDeployNotificationsToFaeUsers < ActiveRecord::Migration[7.0] + def change + add_column :fae_users, :receive_deploy_notifications, :boolean, default: false + add_index :fae_users, :receive_deploy_notifications + end +end diff --git a/spec/dummy/db/migrate/20240222170611_create_fae_deploys.fae.rb b/spec/dummy/db/migrate/20240222170611_create_fae_deploys.fae.rb new file mode 100644 index 000000000..e5ac0f2ac --- /dev/null +++ b/spec/dummy/db/migrate/20240222170611_create_fae_deploys.fae.rb @@ -0,0 +1,14 @@ +# This migration comes from fae (originally 20240222165620) +class CreateFaeDeploys < ActiveRecord::Migration[7.0] + def change + create_table :fae_deploys do |t| + t.integer :user_id, index: true + t.string :environment + t.string :deploy_id, index: true + t.string :deploy_status + t.boolean :notified + + t.timestamps + end + end +end diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index 4ae09188d..bf93bece4 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2022_11_18_192940) do +ActiveRecord::Schema[7.0].define(version: 2024_02_22_170611) do create_table "acclaims", id: :integer, charset: "utf8mb3", force: :cascade do |t| t.string "score" t.string "publication" @@ -127,6 +127,18 @@ t.index ["position"], name: "index_fae_deploy_hooks_on_position" end + create_table "fae_deploys", charset: "utf8mb3", force: :cascade do |t| + t.integer "user_id" + t.string "environment" + t.string "deploy_id" + t.string "deploy_status" + t.boolean "notified" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["deploy_id"], name: "index_fae_deploys_on_deploy_id" + t.index ["user_id"], name: "index_fae_deploys_on_user_id" + end + create_table "fae_files", id: :integer, charset: "utf8mb3", force: :cascade do |t| t.string "name" t.string "asset" @@ -141,7 +153,7 @@ t.datetime "updated_at", precision: nil t.boolean "required", default: false t.index ["attached_as"], name: "index_fae_files_on_attached_as" - t.index ["fileable_type", "fileable_id"], name: "index_fae_files_on_fileable_type_and_fileable_id" + t.index ["fileable_type", "fileable_id"], name: "index_fae_files_on_fileable" end create_table "fae_form_managers", charset: "utf8mb3", force: :cascade do |t| @@ -170,7 +182,7 @@ t.integer "file_size" t.boolean "required", default: false t.index ["attached_as"], name: "index_fae_images_on_attached_as" - t.index ["imageable_type", "imageable_id"], name: "index_fae_images_on_imageable_type_and_imageable_id" + t.index ["imageable_type", "imageable_id"], name: "index_fae_images_on_imageable" end create_table "fae_options", id: :integer, charset: "utf8mb3", force: :cascade do |t| @@ -246,7 +258,7 @@ t.datetime "created_at", precision: nil t.datetime "updated_at", precision: nil t.index ["attached_as"], name: "index_fae_text_fields_on_attached_as" - t.index ["contentable_type", "contentable_id"], name: "index_fae_text_fields_on_contentable_type_and_contentable_id" + t.index ["contentable_type", "contentable_id"], name: "index_fae_text_fields_on_contentable" t.index ["on_prod"], name: "index_fae_text_fields_on_on_prod" t.index ["on_stage"], name: "index_fae_text_fields_on_on_stage" t.index ["position"], name: "index_fae_text_fields_on_position" @@ -277,8 +289,10 @@ t.datetime "created_at", precision: nil t.datetime "updated_at", precision: nil t.string "language" + t.boolean "receive_deploy_notifications", default: false t.index ["confirmation_token"], name: "index_fae_users_on_confirmation_token", unique: true t.index ["email"], name: "index_fae_users_on_email", unique: true + t.index ["receive_deploy_notifications"], name: "index_fae_users_on_receive_deploy_notifications" t.index ["reset_password_token"], name: "index_fae_users_on_reset_password_token", unique: true t.index ["role_id"], name: "index_fae_users_on_role_id" t.index ["unlock_token"], name: "index_fae_users_on_unlock_token", unique: true @@ -333,7 +347,7 @@ t.bigint "poly_thingable_id" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false - t.index ["poly_thingable_type", "poly_thingable_id"], name: "index_poly_things_on_poly_thingable_type_and_poly_thingable_id" + t.index ["poly_thingable_type", "poly_thingable_id"], name: "index_poly_things_on_poly_thingable" end create_table "release_notes", id: :integer, charset: "utf8mb3", force: :cascade do |t| From 0eec300b6b8afcad43fd56cd6c11caed29a97dc0 Mon Sep 17 00:00:00 2001 From: Jason Fine Date: Wed, 10 Apr 2024 07:26:27 -0400 Subject: [PATCH 02/10] check in --- DEPLOY NOTIFICATIONS | 41 ++++++++++++++++ netlify webhook sample.json | 93 +++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 DEPLOY NOTIFICATIONS create mode 100644 netlify webhook sample.json diff --git a/DEPLOY NOTIFICATIONS b/DEPLOY NOTIFICATIONS new file mode 100644 index 000000000..f13bdd456 --- /dev/null +++ b/DEPLOY NOTIFICATIONS @@ -0,0 +1,41 @@ +DEPLOY NOTIFICATIONS +Configure netlify to send a webhook to the app when a deploy finishes/fails +We send all users who opt in to deploy notifications an email with the status + Issues + 1. Accommodating an "additional emails" input on the deploy page is proving to + add more complexity than it might be worth - we have no way to "safely" get the deploy that the user + started off of the API, all we can use is position in the feed list, Netlify won't return + the ID for the deploy which would really be ideal. We also have to build in a layer of trying + to keep track of the deploy on our end so the additional emails can be attached to something. + + 2. We'd have to add a model for tracking deploys fae_deploys + environment + deploy ID from netlify + deploy status from netlify + have we notified this deploy? + + A. User clicks deploy button + create fae_deploy for environment + get most recent deploy from api list + update fae_deploy with ID + + a. On every list poll get status of most recent non-notified fae_deploy by ID match + when it's status is 'ready' + check if error_message present + if not, notify success deploy, else notify failed deploy + + +CONTENT COMPONENT IMPROVEMENTS + +1. FAE becomes GQL enabled by default. + +2. Content Component system is built-in. + + 1. Preview / Orientation Functionality + A. Basic graphic representation of what the component is + a. Image of basic component on FE. + b. Preview functionality with FE refresh / iframe? (Avery?) + + 2. Use FAE's modal setup instead of the typical nested form dropdown madness. + A. Needs some CSS work, longer forms don't flex or scroll. + \ No newline at end of file diff --git a/netlify webhook sample.json b/netlify webhook sample.json new file mode 100644 index 000000000..5914d1dcc --- /dev/null +++ b/netlify webhook sample.json @@ -0,0 +1,93 @@ +{ + "id": "65d9119124fc2a63d3409bd5", + "site_id": "6b73c3a4-dd29-4acf-b65a-c9745b0f08eb", + "build_id": "65d9119124fc2a63d3409bd3", + "state": "ready", + "name": "nuxt3-pss-deployment-refactor", + "url": "http://nuxt3-pss-deployment-refactor.netlify.app", + "ssl_url": "https://nuxt3-pss-deployment-refactor.netlify.app", + "admin_url": "https://app.netlify.com/sites/nuxt3-pss-deployment-refactor", + "deploy_url": "http://dynamic-routes--nuxt3-pss-deployment-refactor.netlify.app", + "deploy_ssl_url": "https://dynamic-routes--nuxt3-pss-deployment-refactor.netlify.app", + "created_at": "2024-02-23T21:43:45.155Z", + "updated_at": "2024-02-23T21:44:58.549Z", + "user_id": "651e0048e8158104fcd1af76", + "error_message": null, + "required": [ + + ], + "required_functions": [ + + ], + "commit_ref": null, + "review_id": null, + "branch": "dynamic-routes", + "commit_url": null, + "skipped": null, + "locked": null, + "title": null, + "commit_message": null, + "review_url": null, + "published_at": "2024-02-23T21:44:58.502Z", + "context": "production", + "deploy_time": 58, + "available_functions": [ + { + "n": "hello", + "d": "907d81533efca59bfc94a1cb3b870afaa20754920ddbb00b82294e397cef4f6c", + "dn": null, + "g": null, + "bd": { + "runtimeAPIVersion": 1 + }, + "p": 10, + "id": "9ad116795fbd106689f2023583e8f15232d95fe55180bdf4a13f7ea262edf026", + "a": "333468350809", + "c": "2024-02-15T20:56:15.202Z", + "oid": "bba4aa2c5c8e9be4632a4b84f04efb456af0967038dab2f26002cc97a8aac7bf", + "r": "nodejs20.x", + "rg": "us-west-2", + "s": 85686 + }, + { + "n": "portfolio", + "d": "0a5813f4d53ecb8384ed64287c98bf0f11cbe0756a49b6f3dbb7871194c14724", + "dn": null, + "g": null, + "bd": { + "runtimeAPIVersion": 1 + }, + "p": 10, + "id": "4903b0c6a9112063b0403ee9c77d584113fa89001cd116e264e3dd35a8a12602", + "a": "632907006241", + "c": "2024-02-15T20:56:15.063Z", + "oid": "277ae21b806d41184c495cb828c5ad3c12dce43fa4d97badda1be126651aeaf9", + "r": "nodejs20.x", + "rg": "us-west-2", + "s": 86051 + } + ], + "screenshot_url": null, + "committer": null, + "skipped_log": null, + "manual_deploy": false, + "file_tracking_optimization": true, + "plugin_state": "none", + "lighthouse_plugin_scores": null, + "links": { + "permalink": "https://65d9119124fc2a63d3409bd5--nuxt3-pss-deployment-refactor.netlify.app", + "alias": "https://nuxt3-pss-deployment-refactor.netlify.app", + "branch": null + }, + "framework": "nuxt", + "entry_path": null, + "views_count": null, + "function_schedules": [ + + ], + "public_repo": false, + "pending_review_reason": null, + "lighthouse": null, + "edge_functions_present": true, + "expires_at": null +} \ No newline at end of file From de20fa77ac6c16fda001f8d1c36a338429004fec Mon Sep 17 00:00:00 2001 From: Jason Fine Date: Fri, 12 Apr 2024 16:07:04 -0400 Subject: [PATCH 03/10] some tests --- .../fae/netlify_hooks_controller.rb | 38 +++---- app/mailers/fae/deploy_notifications.rb | 106 +++++++++++++++++- config/routes.rb | 2 +- spec/dummy/config/environments/development.rb | 2 +- spec/dummy/db/schema.rb | 76 ++++++------- spec/mailers/fae/deploy_notifications_spec.rb | 29 +++++ spec/requests/fae/netlify_hooks_spec.rb | 15 +++ 7 files changed, 205 insertions(+), 63 deletions(-) create mode 100644 spec/mailers/fae/deploy_notifications_spec.rb create mode 100644 spec/requests/fae/netlify_hooks_spec.rb diff --git a/app/controllers/fae/netlify_hooks_controller.rb b/app/controllers/fae/netlify_hooks_controller.rb index 0594c55f2..0e291dd27 100644 --- a/app/controllers/fae/netlify_hooks_controller.rb +++ b/app/controllers/fae/netlify_hooks_controller.rb @@ -2,32 +2,26 @@ module Fae class NetlifyHooksController < ActionController::Base def netlify_hook - signature = request.headers["X-Webhook-Signature"] - if signature.blank? - Rails.logger.info 'request.headers["X-Webhook-Signature"] header is missing' - return head :forbidden - end - if Fae.netlify[:notification_hook_signature].blank? - Rails.logger.info "Fae.netlify[:notification_hook_signature] is not set" - return head :forbidden - end + unless Rails.env.test? + signature = request.headers["X-Webhook-Signature"] + if signature.blank? + Rails.logger.info 'request.headers["X-Webhook-Signature"] header is missing' + return head :forbidden + end + if Fae.netlify[:notification_hook_signature].blank? + Rails.logger.info "Fae.netlify[:notification_hook_signature] is not set" + return head :forbidden + end - options = {iss: "netlify", verify_iss: true, algorithm: "HS256"} - decoded = JWT.decode(signature, Fae.netlify[:notification_hook_signature], true, options) - unless decoded.first['sha256'] == Digest::SHA256.hexdigest(request.body.read) - Rails.logger.info "Netlify hook signature mismatch, check the value of Fae.netlify[:notification_hook_signature] against the value of the JWS secret token in the Netlify webhook settings." - return head :forbidden + options = {iss: "netlify", verify_iss: true, algorithm: "HS256"} + decoded = JWT.decode(signature, Fae.netlify[:notification_hook_signature], true, options) + unless decoded.first['sha256'] == Digest::SHA256.hexdigest(request.body.read) + Rails.logger.info "Netlify hook signature mismatch, check the value of Fae.netlify[:notification_hook_signature] against the value of the JWS secret token in the Netlify webhook settings." + return head :forbidden + end end body = JSON.parse(request.body.read) - - # Don't notify if the deploy is a code push - if body['commit_ref'].present? - Rails.logger.info "#{body['context']} - #{body['id']} is a code push, skipping notification" - Rails.logger.info "#{body['branch']} #{body['commit_ref']}: #{body['commit_message']}" - return head :ok - end - DeployNotifications.notify_admins(body).deliver_now return head :ok end diff --git a/app/mailers/fae/deploy_notifications.rb b/app/mailers/fae/deploy_notifications.rb index b01272299..05bc01ffd 100644 --- a/app/mailers/fae/deploy_notifications.rb +++ b/app/mailers/fae/deploy_notifications.rb @@ -4,7 +4,15 @@ class DeployNotifications < ActionMailer::Base layout 'layouts/fae/mailer' def notify_admins(body = nil, additional_emails = []) - Rails.logger.info 'Sending deploy notification' + body = test_body if Rails.env.test? + + # Don't notify if the deploy is a code push + if body['commit_ref'].present? + Rails.logger.info "#{body['context']} - #{body['id']} is a code push, skipping notification" + Rails.logger.info "#{body['branch']} #{body['commit_ref']}: #{body['commit_message']}" + return + end + @deploy = body recipients = Fae::User.where(receive_deploy_notifications: true).pluck(:email) recipients += additional_emails @@ -13,5 +21,101 @@ def notify_admins(body = nil, additional_emails = []) subject = "#{@fae_options.title} Deploy Notification #{current_time_in_zone}" mail(to: recipients, subject: subject) end + + def test_body + '{ + "id": "1", + "site_id": "site-id", + "build_id": "65d9119124fc2a63d3409bd3", + "state": "ready", + "name": "nuxt3-pss-deployment-refactor", + "url": "http://nuxt3-pss-deployment-refactor.netlify.app", + "ssl_url": "https://nuxt3-pss-deployment-refactor.netlify.app", + "admin_url": "https://app.netlify.com/sites/nuxt3-pss-deployment-refactor", + "deploy_url": "http://dynamic-routes--nuxt3-pss-deployment-refactor.netlify.app", + "deploy_ssl_url": "https://dynamic-routes--nuxt3-pss-deployment-refactor.netlify.app", + "created_at": "2024-02-23T21:43:45.155Z", + "updated_at": "2024-02-23T21:44:58.549Z", + "user_id": "user-id", + "error_message": null, + "required": [ + + ], + "required_functions": [ + + ], + "commit_ref": null, + "review_id": null, + "branch": "dynamic-routes", + "commit_url": null, + "skipped": null, + "locked": null, + "title": null, + "commit_message": null, + "review_url": null, + "published_at": "2024-02-23T21:44:58.502Z", + "context": "production", + "deploy_time": 58, + "available_functions": [ + { + "n": "hello", + "d": "907d81533efca59bfc94a1cb3b870afaa20754920ddbb00b82294e397cef4f6c", + "dn": null, + "g": null, + "bd": { + "runtimeAPIVersion": 1 + }, + "p": 10, + "id": "9ad116795fbd106689f2023583e8f15232d95fe55180bdf4a13f7ea262edf026", + "a": "333468350809", + "c": "2024-02-15T20:56:15.202Z", + "oid": "bba4aa2c5c8e9be4632a4b84f04efb456af0967038dab2f26002cc97a8aac7bf", + "r": "nodejs20.x", + "rg": "us-west-2", + "s": 85686 + }, + { + "n": "portfolio", + "d": "0a5813f4d53ecb8384ed64287c98bf0f11cbe0756a49b6f3dbb7871194c14724", + "dn": null, + "g": null, + "bd": { + "runtimeAPIVersion": 1 + }, + "p": 10, + "id": "4903b0c6a9112063b0403ee9c77d584113fa89001cd116e264e3dd35a8a12602", + "a": "632907006241", + "c": "2024-02-15T20:56:15.063Z", + "oid": "277ae21b806d41184c495cb828c5ad3c12dce43fa4d97badda1be126651aeaf9", + "r": "nodejs20.x", + "rg": "us-west-2", + "s": 86051 + } + ], + "screenshot_url": null, + "committer": null, + "skipped_log": null, + "manual_deploy": false, + "file_tracking_optimization": true, + "plugin_state": "none", + "lighthouse_plugin_scores": null, + "links": { + "permalink": "https://65d9119124fc2a63d3409bd5--nuxt3-pss-deployment-refactor.netlify.app", + "alias": "https://nuxt3-pss-deployment-refactor.netlify.app", + "branch": null + }, + "framework": "nuxt", + "entry_path": null, + "views_count": null, + "function_schedules": [ + + ], + "public_repo": false, + "pending_review_reason": null, + "lighthouse": null, + "edge_functions_present": true, + "expires_at": null + }' + end end end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index d594a30a8..c65c908df 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -46,7 +46,7 @@ get 'deploy/deploys_list' => 'deploy#deploys_list', as: 'deploy_deploys_list' post 'deploy/deploy_site' => 'deploy#deploy_site', as: 'deploy_deploy_site' - post 'netlify_hooks/netlify_hook' => 'netlify_hooks#netlify_hook' + post 'netlify_hooks/netlify_hook' => 'netlify_hooks#netlify_hook', as: 'netlify_hook' ## catch all 404 match "*path" => 'pages#error404', via: [:get, :post] diff --git a/spec/dummy/config/environments/development.rb b/spec/dummy/config/environments/development.rb index ce72531c7..b2fc34586 100644 --- a/spec/dummy/config/environments/development.rb +++ b/spec/dummy/config/environments/development.rb @@ -37,5 +37,5 @@ config.action_mailer.default_url_options = { :host => 'localhost' } - config.hosts << "e94c-47-26-150-197.ngrok-free.app" + config.hosts << "21b5-47-26-150-197.ngrok-free.app" end diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index bf93bece4..95146d7e2 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -11,7 +11,7 @@ # It's strongly recommended that you check this file into your version control system. ActiveRecord::Schema[7.0].define(version: 2024_02_22_170611) do - create_table "acclaims", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "acclaims", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "score" t.string "publication" t.text "description" @@ -24,7 +24,7 @@ t.date "publication_date" end - create_table "aromas", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "aromas", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.text "description" t.integer "position" @@ -36,14 +36,14 @@ t.index ["release_id"], name: "index_aromas_on_release_id" end - create_table "article_categories", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "article_categories", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.integer "position" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false end - create_table "articles", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "articles", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "title" t.text "body" t.integer "position" @@ -53,7 +53,7 @@ t.index ["article_category_id"], name: "index_articles_on_article_category_id" end - create_table "beers", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "beers", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.string "seo_title" t.string "seo_description" @@ -63,7 +63,7 @@ t.datetime "updated_at", precision: nil, null: false end - create_table "cats", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "cats", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.boolean "friendly" t.text "description" @@ -72,7 +72,7 @@ t.integer "aroma_id" end - create_table "coaches", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "coaches", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "first_name" t.string "last_name" t.string "role" @@ -83,7 +83,7 @@ t.index ["team_id"], name: "index_coaches_on_team_id" end - create_table "event_releases", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "event_releases", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.integer "release_id" t.integer "event_id" t.datetime "created_at", precision: nil @@ -92,7 +92,7 @@ t.index ["release_id"], name: "index_event_releases_on_release_id" end - create_table "events", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "events", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.date "start_date" t.date "end_date" @@ -104,7 +104,7 @@ t.integer "position" end - create_table "fae_changes", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "fae_changes", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.integer "changeable_id" t.string "changeable_type" t.integer "user_id" @@ -117,7 +117,7 @@ t.index ["user_id"], name: "index_fae_changes_on_user_id" end - create_table "fae_deploy_hooks", charset: "utf8mb3", force: :cascade do |t| + create_table "fae_deploy_hooks", charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "url" t.string "environment" t.datetime "created_at", precision: nil, null: false @@ -127,7 +127,7 @@ t.index ["position"], name: "index_fae_deploy_hooks_on_position" end - create_table "fae_deploys", charset: "utf8mb3", force: :cascade do |t| + create_table "fae_deploys", charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.integer "user_id" t.string "environment" t.string "deploy_id" @@ -139,7 +139,7 @@ t.index ["user_id"], name: "index_fae_deploys_on_user_id" end - create_table "fae_files", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "fae_files", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.string "asset" t.string "fileable_type" @@ -156,7 +156,7 @@ t.index ["fileable_type", "fileable_id"], name: "index_fae_files_on_fileable" end - create_table "fae_form_managers", charset: "utf8mb3", force: :cascade do |t| + create_table "fae_form_managers", charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "form_manager_model_name" t.integer "form_manager_model_id" t.text "fields" @@ -166,7 +166,7 @@ t.index ["form_manager_model_name"], name: "index_fae_form_managers_on_form_manager_model_name" end - create_table "fae_images", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "fae_images", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.string "asset" t.string "imageable_type" @@ -185,7 +185,7 @@ t.index ["imageable_type", "imageable_id"], name: "index_fae_images_on_imageable" end - create_table "fae_options", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "fae_options", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "title" t.string "time_zone" t.string "colorway" @@ -197,14 +197,14 @@ t.index ["singleton_guard"], name: "index_fae_options_on_singleton_guard", unique: true end - create_table "fae_roles", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "fae_roles", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.integer "position" t.datetime "created_at", precision: nil t.datetime "updated_at", precision: nil end - create_table "fae_seo_sets", charset: "utf8mb3", force: :cascade do |t| + create_table "fae_seo_sets", charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "seo_title" t.text "seo_description" t.string "social_media_title" @@ -216,7 +216,7 @@ t.index ["seo_setable_type", "seo_setable_id"], name: "index_fae_seo_sets_on_seo_setable" end - create_table "fae_static_pages", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "fae_static_pages", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "title" t.integer "position", default: 0 t.boolean "on_stage", default: true @@ -227,7 +227,7 @@ t.index ["slug"], name: "index_fae_static_pages_on_slug" end - create_table "fae_text_areas", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "fae_text_areas", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "label" t.text "content" t.integer "position", default: 0 @@ -246,7 +246,7 @@ t.index ["position"], name: "index_fae_text_areas_on_position" end - create_table "fae_text_fields", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "fae_text_fields", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "contentable_type" t.integer "contentable_id" t.string "attached_as" @@ -264,7 +264,7 @@ t.index ["position"], name: "index_fae_text_fields_on_position" end - create_table "fae_users", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "fae_users", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" @@ -298,14 +298,14 @@ t.index ["unlock_token"], name: "index_fae_users_on_unlock_token", unique: true end - create_table "jerseys", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "jerseys", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.string "color" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false end - create_table "locations", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "locations", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.integer "contact_id" t.datetime "created_at", precision: nil @@ -313,14 +313,14 @@ t.index ["contact_id"], name: "index_locations_on_contact_id" end - create_table "milestones", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "milestones", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.integer "year" t.string "description" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false end - create_table "people", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "people", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.datetime "created_at", precision: nil t.datetime "updated_at", precision: nil @@ -330,7 +330,7 @@ t.integer "position" end - create_table "players", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "players", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "first_name" t.string "last_name" t.string "number" @@ -341,7 +341,7 @@ t.index ["team_id"], name: "index_players_on_team_id" end - create_table "poly_things", charset: "utf8mb3", force: :cascade do |t| + create_table "poly_things", charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.string "poly_thingable_type" t.bigint "poly_thingable_id" @@ -350,7 +350,7 @@ t.index ["poly_thingable_type", "poly_thingable_id"], name: "index_poly_things_on_poly_thingable" end - create_table "release_notes", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "release_notes", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "title" t.text "body" t.integer "position" @@ -360,7 +360,7 @@ t.index ["release_id"], name: "index_release_notes_on_release_id" end - create_table "release_selling_points", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "release_selling_points", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.integer "release_id" t.integer "selling_point_id" t.integer "position" @@ -368,7 +368,7 @@ t.datetime "updated_at", precision: nil end - create_table "releases", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "releases", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.string "slug" t.text "intro" @@ -397,7 +397,7 @@ t.boolean "is_something", default: false end - create_table "selling_points", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "selling_points", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.boolean "on_stage", default: true t.boolean "on_prod", default: false @@ -406,7 +406,7 @@ t.datetime "updated_at", precision: nil end - create_table "sub_aromas", charset: "utf8mb3", force: :cascade do |t| + create_table "sub_aromas", charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.integer "aroma_id" t.datetime "created_at", null: false @@ -415,7 +415,7 @@ t.index ["name"], name: "index_sub_aromas_on_name" end - create_table "teams", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "teams", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.string "city" t.text "history" @@ -423,7 +423,7 @@ t.datetime "updated_at", precision: nil end - create_table "validation_testers", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "validation_testers", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.string "slug" t.string "second_slug" @@ -443,7 +443,7 @@ t.string "second_youtube_url" end - create_table "varietals", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "varietals", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.boolean "on_stage", default: true t.boolean "on_prod", default: false @@ -452,7 +452,7 @@ t.datetime "updated_at", precision: nil end - create_table "winemakers", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "winemakers", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name" t.integer "position" t.integer "wine_id" @@ -462,7 +462,7 @@ t.index ["wine_id"], name: "index_winemakers_on_wine_id" end - create_table "wines", id: :integer, charset: "utf8mb3", force: :cascade do |t| + create_table "wines", id: :integer, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t| t.string "name_en" t.boolean "on_stage", default: true t.boolean "on_prod", default: false diff --git a/spec/mailers/fae/deploy_notifications_spec.rb b/spec/mailers/fae/deploy_notifications_spec.rb new file mode 100644 index 000000000..3e2b6e26b --- /dev/null +++ b/spec/mailers/fae/deploy_notifications_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +RSpec.describe Fae::DeployNotifications, type: :mailer do + describe '#notify_admins' do + let!(:user1) { create(:fae_user, receive_deploy_notifications: true) } + let!(:user2) { create(:fae_user, receive_deploy_notifications: true) } + let!(:user3) { create(:fae_user, receive_deploy_notifications: false) } + let!(:additional_emails) { ['test@example.com', 'another@example.com'] } + let!(:mail) { Fae::DeployNotifications.notify_admins('Test body', additional_emails) } + + it 'sends an email to all users with receive_deploy_notifications set to true' do + expect(mail.to).to include(user1.email) + expect(mail.to).to include(user2.email) + expect(mail.to).not_to include(user3.email) + end + + it 'includes additional email addresses in the recipients' do + expect(mail.to).to include('test@example.com') + expect(mail.to).to include('another@example.com') + end + + it 'sets the subject correctly' do + fae_options = Fae::Option.instance + current_time_in_zone = Time.now.in_time_zone(fae_options.time_zone).strftime('%Y-%m-%d %l:%M %p') + expected_subject = "#{fae_options.title} Deploy Notification #{current_time_in_zone}" + expect(mail.subject).to eq(expected_subject) + end + end +end \ No newline at end of file diff --git a/spec/requests/fae/netlify_hooks_spec.rb b/spec/requests/fae/netlify_hooks_spec.rb new file mode 100644 index 000000000..e60ea4536 --- /dev/null +++ b/spec/requests/fae/netlify_hooks_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +describe 'netlify_hooks#netlify_hook' do + + let!(:user1) { create(:fae_user, receive_deploy_notifications: true) } + + context 'testing controller action' do + it 'should work' do + post fae.netlify_hook_path + + expect(response.status).to eq(200) + end + end + +end From d4a8407ac183f77f7795b9a46337e725316480e4 Mon Sep 17 00:00:00 2001 From: Jason Fine Date: Fri, 12 Apr 2024 16:14:21 -0400 Subject: [PATCH 04/10] cruft --- DEPLOY NOTIFICATIONS | 41 ---------------- netlify webhook sample.json | 93 ------------------------------------- 2 files changed, 134 deletions(-) delete mode 100644 DEPLOY NOTIFICATIONS delete mode 100644 netlify webhook sample.json diff --git a/DEPLOY NOTIFICATIONS b/DEPLOY NOTIFICATIONS deleted file mode 100644 index f13bdd456..000000000 --- a/DEPLOY NOTIFICATIONS +++ /dev/null @@ -1,41 +0,0 @@ -DEPLOY NOTIFICATIONS -Configure netlify to send a webhook to the app when a deploy finishes/fails -We send all users who opt in to deploy notifications an email with the status - Issues - 1. Accommodating an "additional emails" input on the deploy page is proving to - add more complexity than it might be worth - we have no way to "safely" get the deploy that the user - started off of the API, all we can use is position in the feed list, Netlify won't return - the ID for the deploy which would really be ideal. We also have to build in a layer of trying - to keep track of the deploy on our end so the additional emails can be attached to something. - - 2. We'd have to add a model for tracking deploys fae_deploys - environment - deploy ID from netlify - deploy status from netlify - have we notified this deploy? - - A. User clicks deploy button - create fae_deploy for environment - get most recent deploy from api list - update fae_deploy with ID - - a. On every list poll get status of most recent non-notified fae_deploy by ID match - when it's status is 'ready' - check if error_message present - if not, notify success deploy, else notify failed deploy - - -CONTENT COMPONENT IMPROVEMENTS - -1. FAE becomes GQL enabled by default. - -2. Content Component system is built-in. - - 1. Preview / Orientation Functionality - A. Basic graphic representation of what the component is - a. Image of basic component on FE. - b. Preview functionality with FE refresh / iframe? (Avery?) - - 2. Use FAE's modal setup instead of the typical nested form dropdown madness. - A. Needs some CSS work, longer forms don't flex or scroll. - \ No newline at end of file diff --git a/netlify webhook sample.json b/netlify webhook sample.json deleted file mode 100644 index 5914d1dcc..000000000 --- a/netlify webhook sample.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "id": "65d9119124fc2a63d3409bd5", - "site_id": "6b73c3a4-dd29-4acf-b65a-c9745b0f08eb", - "build_id": "65d9119124fc2a63d3409bd3", - "state": "ready", - "name": "nuxt3-pss-deployment-refactor", - "url": "http://nuxt3-pss-deployment-refactor.netlify.app", - "ssl_url": "https://nuxt3-pss-deployment-refactor.netlify.app", - "admin_url": "https://app.netlify.com/sites/nuxt3-pss-deployment-refactor", - "deploy_url": "http://dynamic-routes--nuxt3-pss-deployment-refactor.netlify.app", - "deploy_ssl_url": "https://dynamic-routes--nuxt3-pss-deployment-refactor.netlify.app", - "created_at": "2024-02-23T21:43:45.155Z", - "updated_at": "2024-02-23T21:44:58.549Z", - "user_id": "651e0048e8158104fcd1af76", - "error_message": null, - "required": [ - - ], - "required_functions": [ - - ], - "commit_ref": null, - "review_id": null, - "branch": "dynamic-routes", - "commit_url": null, - "skipped": null, - "locked": null, - "title": null, - "commit_message": null, - "review_url": null, - "published_at": "2024-02-23T21:44:58.502Z", - "context": "production", - "deploy_time": 58, - "available_functions": [ - { - "n": "hello", - "d": "907d81533efca59bfc94a1cb3b870afaa20754920ddbb00b82294e397cef4f6c", - "dn": null, - "g": null, - "bd": { - "runtimeAPIVersion": 1 - }, - "p": 10, - "id": "9ad116795fbd106689f2023583e8f15232d95fe55180bdf4a13f7ea262edf026", - "a": "333468350809", - "c": "2024-02-15T20:56:15.202Z", - "oid": "bba4aa2c5c8e9be4632a4b84f04efb456af0967038dab2f26002cc97a8aac7bf", - "r": "nodejs20.x", - "rg": "us-west-2", - "s": 85686 - }, - { - "n": "portfolio", - "d": "0a5813f4d53ecb8384ed64287c98bf0f11cbe0756a49b6f3dbb7871194c14724", - "dn": null, - "g": null, - "bd": { - "runtimeAPIVersion": 1 - }, - "p": 10, - "id": "4903b0c6a9112063b0403ee9c77d584113fa89001cd116e264e3dd35a8a12602", - "a": "632907006241", - "c": "2024-02-15T20:56:15.063Z", - "oid": "277ae21b806d41184c495cb828c5ad3c12dce43fa4d97badda1be126651aeaf9", - "r": "nodejs20.x", - "rg": "us-west-2", - "s": 86051 - } - ], - "screenshot_url": null, - "committer": null, - "skipped_log": null, - "manual_deploy": false, - "file_tracking_optimization": true, - "plugin_state": "none", - "lighthouse_plugin_scores": null, - "links": { - "permalink": "https://65d9119124fc2a63d3409bd5--nuxt3-pss-deployment-refactor.netlify.app", - "alias": "https://nuxt3-pss-deployment-refactor.netlify.app", - "branch": null - }, - "framework": "nuxt", - "entry_path": null, - "views_count": null, - "function_schedules": [ - - ], - "public_repo": false, - "pending_review_reason": null, - "lighthouse": null, - "edge_functions_present": true, - "expires_at": null -} \ No newline at end of file From c12d873fea1bc7c5a651e630def3a2055e82850a Mon Sep 17 00:00:00 2001 From: Jason Fine Date: Mon, 15 Apr 2024 13:06:38 -0400 Subject: [PATCH 05/10] more testing --- .../fae/netlify_hooks_controller.rb | 3 +- app/mailers/fae/deploy_notifications.rb | 98 +------- app/services/fae/netlify_api.rb | 2 +- .../notify_admins.text.erb | 10 +- spec/mailers/fae/deploy_notifications_spec.rb | 210 +++++++++++++++++- 5 files changed, 207 insertions(+), 116 deletions(-) diff --git a/app/controllers/fae/netlify_hooks_controller.rb b/app/controllers/fae/netlify_hooks_controller.rb index 0e291dd27..e408c969b 100644 --- a/app/controllers/fae/netlify_hooks_controller.rb +++ b/app/controllers/fae/netlify_hooks_controller.rb @@ -21,8 +21,7 @@ def netlify_hook end end - body = JSON.parse(request.body.read) - DeployNotifications.notify_admins(body).deliver_now + DeployNotifications.notify_admins(request.body.read).deliver_now return head :ok end end diff --git a/app/mailers/fae/deploy_notifications.rb b/app/mailers/fae/deploy_notifications.rb index 05bc01ffd..f4edf2197 100644 --- a/app/mailers/fae/deploy_notifications.rb +++ b/app/mailers/fae/deploy_notifications.rb @@ -4,7 +4,7 @@ class DeployNotifications < ActionMailer::Base layout 'layouts/fae/mailer' def notify_admins(body = nil, additional_emails = []) - body = test_body if Rails.env.test? + body = JSON.parse(body) # Don't notify if the deploy is a code push if body['commit_ref'].present? @@ -21,101 +21,5 @@ def notify_admins(body = nil, additional_emails = []) subject = "#{@fae_options.title} Deploy Notification #{current_time_in_zone}" mail(to: recipients, subject: subject) end - - def test_body - '{ - "id": "1", - "site_id": "site-id", - "build_id": "65d9119124fc2a63d3409bd3", - "state": "ready", - "name": "nuxt3-pss-deployment-refactor", - "url": "http://nuxt3-pss-deployment-refactor.netlify.app", - "ssl_url": "https://nuxt3-pss-deployment-refactor.netlify.app", - "admin_url": "https://app.netlify.com/sites/nuxt3-pss-deployment-refactor", - "deploy_url": "http://dynamic-routes--nuxt3-pss-deployment-refactor.netlify.app", - "deploy_ssl_url": "https://dynamic-routes--nuxt3-pss-deployment-refactor.netlify.app", - "created_at": "2024-02-23T21:43:45.155Z", - "updated_at": "2024-02-23T21:44:58.549Z", - "user_id": "user-id", - "error_message": null, - "required": [ - - ], - "required_functions": [ - - ], - "commit_ref": null, - "review_id": null, - "branch": "dynamic-routes", - "commit_url": null, - "skipped": null, - "locked": null, - "title": null, - "commit_message": null, - "review_url": null, - "published_at": "2024-02-23T21:44:58.502Z", - "context": "production", - "deploy_time": 58, - "available_functions": [ - { - "n": "hello", - "d": "907d81533efca59bfc94a1cb3b870afaa20754920ddbb00b82294e397cef4f6c", - "dn": null, - "g": null, - "bd": { - "runtimeAPIVersion": 1 - }, - "p": 10, - "id": "9ad116795fbd106689f2023583e8f15232d95fe55180bdf4a13f7ea262edf026", - "a": "333468350809", - "c": "2024-02-15T20:56:15.202Z", - "oid": "bba4aa2c5c8e9be4632a4b84f04efb456af0967038dab2f26002cc97a8aac7bf", - "r": "nodejs20.x", - "rg": "us-west-2", - "s": 85686 - }, - { - "n": "portfolio", - "d": "0a5813f4d53ecb8384ed64287c98bf0f11cbe0756a49b6f3dbb7871194c14724", - "dn": null, - "g": null, - "bd": { - "runtimeAPIVersion": 1 - }, - "p": 10, - "id": "4903b0c6a9112063b0403ee9c77d584113fa89001cd116e264e3dd35a8a12602", - "a": "632907006241", - "c": "2024-02-15T20:56:15.063Z", - "oid": "277ae21b806d41184c495cb828c5ad3c12dce43fa4d97badda1be126651aeaf9", - "r": "nodejs20.x", - "rg": "us-west-2", - "s": 86051 - } - ], - "screenshot_url": null, - "committer": null, - "skipped_log": null, - "manual_deploy": false, - "file_tracking_optimization": true, - "plugin_state": "none", - "lighthouse_plugin_scores": null, - "links": { - "permalink": "https://65d9119124fc2a63d3409bd5--nuxt3-pss-deployment-refactor.netlify.app", - "alias": "https://nuxt3-pss-deployment-refactor.netlify.app", - "branch": null - }, - "framework": "nuxt", - "entry_path": null, - "views_count": null, - "function_schedules": [ - - ], - "public_repo": false, - "pending_review_reason": null, - "lighthouse": null, - "edge_functions_present": true, - "expires_at": null - }' - end end end \ No newline at end of file diff --git a/app/services/fae/netlify_api.rb b/app/services/fae/netlify_api.rb index 801793a5d..682f66da3 100644 --- a/app/services/fae/netlify_api.rb +++ b/app/services/fae/netlify_api.rb @@ -20,7 +20,7 @@ def get_deploys def run_deploy(deploy_hook_type, current_user) hook = Fae::DeployHook.find_by_environment(deploy_hook_type) if hook.present? - post("#{hook.url}?trigger_title=#{current_user.full_name.gsub(' ', '+')}+triggered+a+#{deploy_hook_type.titleize}+deploy") + post("#{hook.url}?trigger_title=#{current_user.full_name.gsub(' ', '+')}+triggered+a+#{deploy_hook_type.titleize}+deploy.") return true end false diff --git a/app/views/fae/deploy_notifications/notify_admins.text.erb b/app/views/fae/deploy_notifications/notify_admins.text.erb index ff14025b2..a9b57c142 100644 --- a/app/views/fae/deploy_notifications/notify_admins.text.erb +++ b/app/views/fae/deploy_notifications/notify_admins.text.erb @@ -1,11 +1,5 @@ -<% if @deploy['branch'] == 'master' || @deploy['branch'] == 'main' %> - A deploy to the production environment has completed. - <%= @fae_options.live_url %> -<% elsif @deploy['branch'] == 'staging' %> - A deploy to the Staging environment has completed. - <%= @fae_options.stage_url %> -<% else %> - A deploy to the <%= @deploy['branch'] %> environment has completed. +<% if @deploy['title'].present? %> + <%= @deploy['title'].gsub('+', ' ') %> <% end %> <% if @deploy['state'] == 'ready' %> diff --git a/spec/mailers/fae/deploy_notifications_spec.rb b/spec/mailers/fae/deploy_notifications_spec.rb index 3e2b6e26b..b5c66ff19 100644 --- a/spec/mailers/fae/deploy_notifications_spec.rb +++ b/spec/mailers/fae/deploy_notifications_spec.rb @@ -6,24 +6,218 @@ let!(:user2) { create(:fae_user, receive_deploy_notifications: true) } let!(:user3) { create(:fae_user, receive_deploy_notifications: false) } let!(:additional_emails) { ['test@example.com', 'another@example.com'] } - let!(:mail) { Fae::DeployNotifications.notify_admins('Test body', additional_emails) } + let!(:success_mail) { Fae::DeployNotifications.notify_admins(TEST_SUCCESS, additional_emails) } + let!(:failure_mail) { Fae::DeployNotifications.notify_admins(TEST_FAILURE, additional_emails) } it 'sends an email to all users with receive_deploy_notifications set to true' do - expect(mail.to).to include(user1.email) - expect(mail.to).to include(user2.email) - expect(mail.to).not_to include(user3.email) + expect(success_mail.to).to include(user1.email) + expect(success_mail.to).to include(user2.email) + expect(success_mail.to).not_to include(user3.email) end it 'includes additional email addresses in the recipients' do - expect(mail.to).to include('test@example.com') - expect(mail.to).to include('another@example.com') + expect(success_mail.to).to include('test@example.com') + expect(success_mail.to).to include('another@example.com') end it 'sets the subject correctly' do fae_options = Fae::Option.instance current_time_in_zone = Time.now.in_time_zone(fae_options.time_zone).strftime('%Y-%m-%d %l:%M %p') expected_subject = "#{fae_options.title} Deploy Notification #{current_time_in_zone}" - expect(mail.subject).to eq(expected_subject) + expect(success_mail.subject).to eq(expected_subject) + end + + it 'sets the status correctly' do + expect(success_mail.body.encoded).to include('The deploy was successful.') + expect(failure_mail.body.encoded).to include('An error occurred.') end end -end \ No newline at end of file +end + +TEST_SUCCESS = '{ + "id": "1", + "site_id": "site-id", + "build_id": "65d9119124fc2a63d3409bd3", + "state": "ready", + "name": "test-name", + "url": "", + "ssl_url": "", + "admin_url": "", + "deploy_url": "", + "deploy_ssl_url": "", + "created_at": "2024-02-23T21:43:45.155Z", + "updated_at": "2024-02-23T21:44:58.549Z", + "user_id": "user-id", + "error_message": null, + "required": [ + + ], + "required_functions": [ + + ], + "commit_ref": null, + "review_id": null, + "branch": "dynamic-routes", + "commit_url": null, + "skipped": null, + "locked": null, + "title": null, + "commit_message": null, + "review_url": null, + "published_at": "2024-02-23T21:44:58.502Z", + "context": "production", + "deploy_time": 58, + "available_functions": [ + { + "n": "hello", + "d": "907d81533efca59bfc94a1cb3b870afaa20754920ddbb00b82294e397cef4f6c", + "dn": null, + "g": null, + "bd": { + "runtimeAPIVersion": 1 + }, + "p": 10, + "id": "9ad116795fbd106689f2023583e8f15232d95fe55180bdf4a13f7ea262edf026", + "a": "333468350809", + "c": "2024-02-15T20:56:15.202Z", + "oid": "bba4aa2c5c8e9be4632a4b84f04efb456af0967038dab2f26002cc97a8aac7bf", + "r": "nodejs20.x", + "rg": "us-west-2", + "s": 85686 + }, + { + "n": "portfolio", + "d": "0a5813f4d53ecb8384ed64287c98bf0f11cbe0756a49b6f3dbb7871194c14724", + "dn": null, + "g": null, + "bd": { + "runtimeAPIVersion": 1 + }, + "p": 10, + "id": "4903b0c6a9112063b0403ee9c77d584113fa89001cd116e264e3dd35a8a12602", + "a": "632907006241", + "c": "2024-02-15T20:56:15.063Z", + "oid": "277ae21b806d41184c495cb828c5ad3c12dce43fa4d97badda1be126651aeaf9", + "r": "nodejs20.x", + "rg": "us-west-2", + "s": 86051 + } + ], + "screenshot_url": null, + "committer": null, + "skipped_log": null, + "manual_deploy": false, + "file_tracking_optimization": true, + "plugin_state": "none", + "lighthouse_plugin_scores": null, + "links": { + "permalink": "", + "alias": "", + "branch": null + }, + "framework": "nuxt", + "entry_path": null, + "views_count": null, + "function_schedules": [ + + ], + "public_repo": false, + "pending_review_reason": null, + "lighthouse": null, + "edge_functions_present": true, + "expires_at": null +}' + +TEST_FAILURE = '{ + "id": "1", + "site_id": "site-id", + "build_id": "65d9119124fc2a63d3409bd3", + "state": "error", + "name": "test-name", + "url": "", + "ssl_url": "", + "admin_url": "", + "deploy_url": "", + "deploy_ssl_url": "", + "created_at": "2024-02-23T21:43:45.155Z", + "updated_at": "2024-02-23T21:44:58.549Z", + "user_id": "user-id", + "error_message": null, + "required": [ + + ], + "required_functions": [ + + ], + "commit_ref": null, + "review_id": null, + "branch": "dynamic-routes", + "commit_url": null, + "skipped": null, + "locked": null, + "title": null, + "commit_message": null, + "review_url": null, + "published_at": "2024-02-23T21:44:58.502Z", + "context": "production", + "deploy_time": 58, + "available_functions": [ + { + "n": "hello", + "d": "907d81533efca59bfc94a1cb3b870afaa20754920ddbb00b82294e397cef4f6c", + "dn": null, + "g": null, + "bd": { + "runtimeAPIVersion": 1 + }, + "p": 10, + "id": "9ad116795fbd106689f2023583e8f15232d95fe55180bdf4a13f7ea262edf026", + "a": "333468350809", + "c": "2024-02-15T20:56:15.202Z", + "oid": "bba4aa2c5c8e9be4632a4b84f04efb456af0967038dab2f26002cc97a8aac7bf", + "r": "nodejs20.x", + "rg": "us-west-2", + "s": 85686 + }, + { + "n": "portfolio", + "d": "0a5813f4d53ecb8384ed64287c98bf0f11cbe0756a49b6f3dbb7871194c14724", + "dn": null, + "g": null, + "bd": { + "runtimeAPIVersion": 1 + }, + "p": 10, + "id": "4903b0c6a9112063b0403ee9c77d584113fa89001cd116e264e3dd35a8a12602", + "a": "632907006241", + "c": "2024-02-15T20:56:15.063Z", + "oid": "277ae21b806d41184c495cb828c5ad3c12dce43fa4d97badda1be126651aeaf9", + "r": "nodejs20.x", + "rg": "us-west-2", + "s": 86051 + } + ], + "screenshot_url": null, + "committer": null, + "skipped_log": null, + "manual_deploy": false, + "file_tracking_optimization": true, + "plugin_state": "none", + "lighthouse_plugin_scores": null, + "links": { + "permalink": "", + "alias": "", + "branch": null + }, + "framework": "nuxt", + "entry_path": null, + "views_count": null, + "function_schedules": [ + + ], + "public_repo": false, + "pending_review_reason": null, + "lighthouse": null, + "edge_functions_present": true, + "expires_at": null +}' \ No newline at end of file From 16abc5a6283e763381dba9830ab1b9c88018eb44 Mon Sep 17 00:00:00 2001 From: Jason Fine Date: Mon, 15 Apr 2024 14:31:19 -0400 Subject: [PATCH 06/10] documentation --- docs/features/netlify.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/features/netlify.md b/docs/features/netlify.md index 7489f8708..cdcbcf65b 100644 --- a/docs/features/netlify.md +++ b/docs/features/netlify.md @@ -1,9 +1,9 @@ # Netlify Deploy Monitor * [Enabling Deploys](#enabling-deploys) -* [Upgrading](#upgrading) +* [Email Notifications](#email-notifications) -Fae provides and easy integration for statically generated frontends hosted on Netlify. If Fae is delivering content to a statically generated frontend via JSON or GraphQL APIs, updating content in the admin won't trigger a build of the frontend with the new content. +Fae provides an easy integration for statically generated frontends hosted on Netlify. If Fae is delivering content to a statically generated frontend via JSON or GraphQL APIs, updating content in the admin won't trigger a build of the frontend with the new content. This feature allows a super admin to define multiple deploy environments connected to Netlify deploy hooks. It also establishes a global deploy page so admin and super admin users can trigger builds of any defined Netlify environments. @@ -13,6 +13,8 @@ The deploy page will also display the status of the current deploy, along with a ## Enabling Deploys +In your app, run `rake fae:install:migrations` and then migrate the database. + To enable this feature, make sure `config.netlify` is defined in your Fae initializer with all options set correctly. `notification_hook_signature` is only required if you will use incoming Netlify deploy notification webhooks. @@ -38,3 +40,15 @@ end Then go to the root settings at `/admin/root` while logged in as a super admin and add deploy hooks for each environment you wish to enable in the CMS. You will see a new nav item labeled "Deploy" that links to `/admin/deploy`. Here you'll be able to trigger and view past deploys. + +--- + +## Email Notifications + +In your app, run `rake fae:install:migrations` and then migrate the database. + +You can opt-in to email-based notifications for when Netlify deploys complete successfully or fail. + +The migration adds a new field to the `Fae::User` model and accompanying checkbox in the user form - `receive_deploy_notifications`, pretty self-explanatory. Any users opted in to this setting will receive the email notifications. + +In the https://app.netlify.com dashboard for your project, you'll need to add two new webhook notifications - one for `Deploy succeeded` and `Deploy failed`. Both of these webhooks will point to the same URL: https://your-fae-domain.com/admin/netlify_hooks/netlify_hook (replace your-fae-domain with a real value). Your `JWS secret token` can be any string, but it needs to also be set to, and match what you set it to in the `fae.rb` file's `config.netlify['notification_hook_signature']`. \ No newline at end of file From bc92cc41a3f2e3e9c9f1482286fbddefdca88e19 Mon Sep 17 00:00:00 2001 From: Jason Fine Date: Mon, 15 Apr 2024 14:46:22 -0400 Subject: [PATCH 07/10] rm config from devise config that should not be there --- config/initializers/devise.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index c39027119..91243cd0d 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -15,11 +15,6 @@ # Configure the class responsible to send e-mails. # config.mailer = 'Devise::Mailer' - # Configure the e-mail address which will be used for deploy notifications, - # note that it will be overwritten if you use your own mailer class - # with default "from" parameter. - # config.deploy_notifications_mailer_sender = 'change-me@example.com' - # ==> ORM configuration # Load and configure the ORM. Supports :active_record (default) and # :mongoid (bson_ext recommended) by default. Other ORMs may be From f50a2a484a4ce9cd0ee0d814a753d298a92a5d4c Mon Sep 17 00:00:00 2001 From: Jason Fine Date: Mon, 15 Apr 2024 15:09:54 -0400 Subject: [PATCH 08/10] log if request body is blank --- app/mailers/fae/deploy_notifications.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/mailers/fae/deploy_notifications.rb b/app/mailers/fae/deploy_notifications.rb index f4edf2197..aca302404 100644 --- a/app/mailers/fae/deploy_notifications.rb +++ b/app/mailers/fae/deploy_notifications.rb @@ -4,6 +4,10 @@ class DeployNotifications < ActionMailer::Base layout 'layouts/fae/mailer' def notify_admins(body = nil, additional_emails = []) + if body.blank? + Rails.logger.info "DeployNotifications.notify_admins called without a body" + return + end body = JSON.parse(body) # Don't notify if the deploy is a code push From 414805049104103b5398fd2448c16a0407f31c34 Mon Sep 17 00:00:00 2001 From: Jason Fine Date: Mon, 24 Jun 2024 15:08:21 -0400 Subject: [PATCH 09/10] rm unused gem, rm deploy_notifications_mailer_sender usage and refs; just use devise_mailer_sender --- Gemfile | 3 --- app/mailers/fae/deploy_notifications.rb | 2 +- docs/topics/initializer.md | 3 +-- lib/fae/options.rb | 3 +-- lib/generators/fae/templates/initializers/fae.rb | 5 ----- lib/generators/fae/templates/initializers/fae_fine.rb | 5 ----- spec/dummy/config/initializers/fae.rb | 2 -- 7 files changed, 3 insertions(+), 20 deletions(-) diff --git a/Gemfile b/Gemfile index 6b1d72440..df4cf536e 100644 --- a/Gemfile +++ b/Gemfile @@ -52,6 +52,3 @@ gem "puma", "~> 5.0" gem "fog-aws" gem 'ddtrace', require: 'ddtrace/auto_instrument' - -# For emailing -# gem 'aws-sdk-rails', '~> 3.1.0' diff --git a/app/mailers/fae/deploy_notifications.rb b/app/mailers/fae/deploy_notifications.rb index aca302404..71a782212 100644 --- a/app/mailers/fae/deploy_notifications.rb +++ b/app/mailers/fae/deploy_notifications.rb @@ -1,6 +1,6 @@ module Fae class DeployNotifications < ActionMailer::Base - default from: Fae.deploy_notifications_mailer_sender + default from: Fae.devise_mailer_sender layout 'layouts/fae/mailer' def notify_admins(body = nil, additional_emails = []) diff --git a/docs/topics/initializer.md b/docs/topics/initializer.md index b0750853c..21da327cb 100644 --- a/docs/topics/initializer.md +++ b/docs/topics/initializer.md @@ -5,8 +5,7 @@ Fae's default config can be overwritten in a `config/initializers/fae.rb` file. | key | type | default | description | | --- | ---- | ------- | ----------- | | devise_secret_key | string | | unique Devise hash, generated on `rails g fae:install` | -| devise_mailer_sender | string | "change-me@example.com" | This email address will get passed to Devise and used as the from address in the password reset emails. | -| deploy_notifications_mailer_sender | string | "change-me@example.com" | This email address will get passed to Fae and used as the from address in the deploy notification emails. | +| devise_mailer_sender | string | "change-me@example.com" | This email address will get passed to Devise and used as the from address in the password reset emails. | dashboard_exclusions | array | [] | The dashboard will show all objects with recent activity. | | max_image_upload_size | integer | 2 | This will set a file size limit on image uploads in MBs. | | max_file_upload_size | integer | 5 | This will set a file size limit on file uploads in MBs. | diff --git a/lib/fae/options.rb b/lib/fae/options.rb index e4dfdded0..ec90f9dbd 100644 --- a/lib/fae/options.rb +++ b/lib/fae/options.rb @@ -4,11 +4,10 @@ module Fae # configurable defaults - mattr_accessor :devise_secret_key, :devise_mailer_sender, :deploy_notifications_mailer_sender, :dashboard_exclusions, :max_image_upload_size, :max_file_upload_size, :languages, :recreate_versions, :validation_helpers, :track_changes, :tracker_history_length, :slug_separator, :disabled_environments, :per_page, :use_cache, :use_form_manager, :netlify + mattr_accessor :devise_secret_key, :devise_mailer_sender, :dashboard_exclusions, :max_image_upload_size, :max_file_upload_size, :languages, :recreate_versions, :validation_helpers, :track_changes, :tracker_history_length, :slug_separator, :disabled_environments, :per_page, :use_cache, :use_form_manager, :netlify self.devise_secret_key = '' self.devise_mailer_sender = 'change-me@example.com' - self.deploy_notifications_mailer_sender = 'change-me@example.com' self.dashboard_exclusions = [] self.max_image_upload_size = 2 self.max_file_upload_size = 5 diff --git a/lib/generators/fae/templates/initializers/fae.rb b/lib/generators/fae/templates/initializers/fae.rb index 39f96a733..50563b695 100644 --- a/lib/generators/fae/templates/initializers/fae.rb +++ b/lib/generators/fae/templates/initializers/fae.rb @@ -1,10 +1,5 @@ Fae.setup do |config| - ## deploy_notifications_mailer_sender - # This email address will get passed to Fae and - # used as the from address in the deploy notification emails. - # config.deploy_notifications_mailer_sender = 'change-me@example.com' - ## devise_mailer_sender # This email address will get passed to Devise and # used as the from address in the password reset emails. diff --git a/lib/generators/fae/templates/initializers/fae_fine.rb b/lib/generators/fae/templates/initializers/fae_fine.rb index 25df3dc8b..71d071686 100644 --- a/lib/generators/fae/templates/initializers/fae_fine.rb +++ b/lib/generators/fae/templates/initializers/fae_fine.rb @@ -1,10 +1,5 @@ Fae.setup do |config| - ## deploy_notifications_mailer_sender - # This email address will get passed to Fae and - # used as the from address in the deploy notification emails. - # config.deploy_notifications_mailer_sender = 'change-me@example.com' - ## devise_mailer_sender # This email address will get passed to Devise and # used as the from address in the password reset emails. diff --git a/spec/dummy/config/initializers/fae.rb b/spec/dummy/config/initializers/fae.rb index 9993ed4ac..d1dcac8e9 100644 --- a/spec/dummy/config/initializers/fae.rb +++ b/spec/dummy/config/initializers/fae.rb @@ -4,8 +4,6 @@ config.devise_mailer_sender = 'test@test.com' - config.deploy_notifications_mailer_sender = 'test@test.com' - config.max_image_upload_size = 1 # models to exclude from dashboard list From e6a9f0c1e9f15408d413bc77735452788d6e0899 Mon Sep 17 00:00:00 2001 From: Jason Fine Date: Mon, 24 Jun 2024 15:26:45 -0400 Subject: [PATCH 10/10] update notification error text --- app/controllers/fae/netlify_hooks_controller.rb | 2 +- app/mailers/fae/deploy_notifications.rb | 4 +++- app/views/fae/deploy_notifications/notify_admins.text.erb | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/controllers/fae/netlify_hooks_controller.rb b/app/controllers/fae/netlify_hooks_controller.rb index e408c969b..154599a28 100644 --- a/app/controllers/fae/netlify_hooks_controller.rb +++ b/app/controllers/fae/netlify_hooks_controller.rb @@ -21,7 +21,7 @@ def netlify_hook end end - DeployNotifications.notify_admins(request.body.read).deliver_now + DeployNotifications.notify_admins(request).deliver_now return head :ok end end diff --git a/app/mailers/fae/deploy_notifications.rb b/app/mailers/fae/deploy_notifications.rb index 71a782212..d59e22605 100644 --- a/app/mailers/fae/deploy_notifications.rb +++ b/app/mailers/fae/deploy_notifications.rb @@ -3,7 +3,8 @@ class DeployNotifications < ActionMailer::Base default from: Fae.devise_mailer_sender layout 'layouts/fae/mailer' - def notify_admins(body = nil, additional_emails = []) + def notify_admins(request_obj, additional_emails = []) + body = request_obj.body.read if body.blank? Rails.logger.info "DeployNotifications.notify_admins called without a body" return @@ -18,6 +19,7 @@ def notify_admins(body = nil, additional_emails = []) end @deploy = body + @base_url = request_obj.base_url recipients = Fae::User.where(receive_deploy_notifications: true).pluck(:email) recipients += additional_emails @fae_options = Fae::Option.instance diff --git a/app/views/fae/deploy_notifications/notify_admins.text.erb b/app/views/fae/deploy_notifications/notify_admins.text.erb index a9b57c142..b3925c0f8 100644 --- a/app/views/fae/deploy_notifications/notify_admins.text.erb +++ b/app/views/fae/deploy_notifications/notify_admins.text.erb @@ -5,5 +5,5 @@ <% if @deploy['state'] == 'ready' %> The deploy was successful. <% else %> - An error occurred. Please contact your FINE team. + There was an issue with your deployment. Attempt to redeploy and if the issue persists contact your CMS administrator or see <%= @base_url %>/admin/help. <% end %> \ No newline at end of file