diff --git a/.overcommit.yml b/.overcommit.yml new file mode 100644 index 0000000..5b4dd02 --- /dev/null +++ b/.overcommit.yml @@ -0,0 +1,20 @@ +verify_signatures: false +PreCommit: + RuboCop: + enabled: true + on_warn: fail + + BundlerAudit: + enabled: true + command: [ 'bundle', 'audit', 'check', '--update' ] + on_warn: fail + + Brakeman: + enabled: true + command: [ 'brakeman' ] + on_warn: fail + + RSpec: + enabled: true + command: [ 'bundle', 'exec', 'rspec' ] + on_warn: fail \ No newline at end of file diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..c99d2e7 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/.rubocop.yml b/.rubocop.yml index f39cdeb..b81c14b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -4,5 +4,28 @@ AllCops: Layout/LineLength: Max: 150 -Style/FrozenStringLiteralComment: - Enabled: false \ No newline at end of file +Style/Documentation: + Enabled: false + +Style/NumericLiterals: + Exclude: + - 'test/dummy/db/schema.rb' + +Metrics/BlockLength: + Exclude: + - 'test/dummy/db/schema.rb' + - 'spec/requests/activejob_web/jobs_spec.rb' + - 'spec/requests/activejob_web/job_executions_spec.rb' + - 'spec/requests/activejob_web/job_approval_requests_spec.rb' + - 'test/dummy/db/seeds.rb' + +Metrics/MethodLength: + Exclude: + - 'db/migrate/20231024051840_create_jobs.rb' + - 'test/dummy/db/migrate/20231118052244_create_users.rb' + - 'db/migrate/20231030145254_create_active_storage_tables.active_storage.rb' + - 'db/migrate/20231106105039_create_activejob_web_job_executions.rb' + +Metrics/AbcSize: + Exclude: + - 'db/migrate/20231030145254_create_active_storage_tables.active_storage.rb' diff --git a/Gemfile b/Gemfile index b9e7252..a149efc 100644 --- a/Gemfile +++ b/Gemfile @@ -1,18 +1,32 @@ +# frozen_string_literal: true + source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } # Specify your gem's dependencies in activejob-web.gemspec. + gemspec gem 'puma' gem 'pg' +gem 'image_processing', '>= 1.2' + gem 'sprockets-rails' +group :development do + gem 'brakeman' + gem 'bundler-audit', require: false + gem 'overcommit', '~> 0.60.0' +end + group :development, :test do + gem 'factory_bot_rails' + gem 'rspec-rails' gem 'rubocop', require: false end +gem 'rails-controller-testing', '~> 1.0', '>= 1.0.5' # Start debugger with binding.b [https://github.com/ruby/debug] # gem "debug", ">= 1.0.0" diff --git a/Gemfile.lock b/Gemfile.lock index f549c0e..50e4066 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,70 +7,71 @@ PATH GEM remote: https://rubygems.org/ specs: - actioncable (7.1.1) - actionpack (= 7.1.1) - activesupport (= 7.1.1) + actioncable (7.1.2) + actionpack (= 7.1.2) + activesupport (= 7.1.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.1.1) - actionpack (= 7.1.1) - activejob (= 7.1.1) - activerecord (= 7.1.1) - activestorage (= 7.1.1) - activesupport (= 7.1.1) + actionmailbox (7.1.2) + actionpack (= 7.1.2) + activejob (= 7.1.2) + activerecord (= 7.1.2) + activestorage (= 7.1.2) + activesupport (= 7.1.2) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.1.1) - actionpack (= 7.1.1) - actionview (= 7.1.1) - activejob (= 7.1.1) - activesupport (= 7.1.1) + actionmailer (7.1.2) + actionpack (= 7.1.2) + actionview (= 7.1.2) + activejob (= 7.1.2) + activesupport (= 7.1.2) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.2) - actionpack (7.1.1) - actionview (= 7.1.1) - activesupport (= 7.1.1) + actionpack (7.1.2) + actionview (= 7.1.2) + activesupport (= 7.1.2) nokogiri (>= 1.8.5) + racc rack (>= 2.2.4) rack-session (>= 1.0.1) rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - actiontext (7.1.1) - actionpack (= 7.1.1) - activerecord (= 7.1.1) - activestorage (= 7.1.1) - activesupport (= 7.1.1) + actiontext (7.1.2) + actionpack (= 7.1.2) + activerecord (= 7.1.2) + activestorage (= 7.1.2) + activesupport (= 7.1.2) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.1.1) - activesupport (= 7.1.1) + actionview (7.1.2) + activesupport (= 7.1.2) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (7.1.1) - activesupport (= 7.1.1) + activejob (7.1.2) + activesupport (= 7.1.2) globalid (>= 0.3.6) - activemodel (7.1.1) - activesupport (= 7.1.1) - activerecord (7.1.1) - activemodel (= 7.1.1) - activesupport (= 7.1.1) + activemodel (7.1.2) + activesupport (= 7.1.2) + activerecord (7.1.2) + activemodel (= 7.1.2) + activesupport (= 7.1.2) timeout (>= 0.4.0) - activestorage (7.1.1) - actionpack (= 7.1.1) - activejob (= 7.1.1) - activerecord (= 7.1.1) - activesupport (= 7.1.1) + activestorage (7.1.2) + actionpack (= 7.1.2) + activejob (= 7.1.2) + activerecord (= 7.1.2) + activesupport (= 7.1.2) marcel (~> 1.0) - activesupport (7.1.1) + activesupport (7.1.2) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -81,27 +82,43 @@ GEM mutex_m tzinfo (~> 2.0) ast (2.4.2) - base64 (0.1.1) + base64 (0.2.0) bigdecimal (3.1.4) + brakeman (6.0.1) builder (3.2.4) + bundler-audit (0.9.1) + bundler (>= 1.2.0, < 3) + thor (~> 1.0) + childprocess (4.1.0) concurrent-ruby (1.2.2) connection_pool (2.4.1) crass (1.0.6) - date (3.3.3) - drb (2.1.1) + date (3.3.4) + diff-lcs (1.5.0) + drb (2.2.0) ruby2_keywords erubi (1.12.0) + factory_bot (6.2.1) + activesupport (>= 5.0.0) + factory_bot_rails (6.2.0) + factory_bot (~> 6.2.0) + railties (>= 5.0.0) + ffi (1.16.3) globalid (1.2.1) activesupport (>= 6.1) i18n (1.14.1) concurrent-ruby (~> 1.0) + image_processing (1.12.2) + mini_magick (>= 4.9.5, < 5) + ruby-vips (>= 2.0.17, < 3) + iniparse (1.5.0) io-console (0.6.0) - irb (1.8.3) + irb (1.9.0) rdoc reline (>= 0.3.8) json (2.6.3) language_server-protocol (3.17.0.3) - loofah (2.21.4) + loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -110,21 +127,26 @@ GEM net-pop net-smtp marcel (1.0.2) + mini_magick (4.12.0) mini_mime (1.1.5) minitest (5.20.0) - mutex_m (0.1.2) - net-imap (0.4.2) + mutex_m (0.2.0) + net-imap (0.4.5) date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.2.1) + net-protocol (0.2.2) timeout net-smtp (0.4.0) net-protocol - nio4r (2.5.9) + nio4r (2.6.0) nokogiri (1.15.4-x86_64-darwin) racc (~> 1.4) + overcommit (0.60.0) + childprocess (>= 0.6.3, < 5) + iniparse (~> 1.4) + rexml (~> 3.2) parallel (1.23.0) parser (3.2.2.4) ast (~> 2.4.1) @@ -134,7 +156,7 @@ GEM stringio puma (6.4.0) nio4r (~> 2.0) - racc (1.7.1) + racc (1.7.3) rack (3.0.8) rack-session (2.0.0) rack (>= 3.0.0) @@ -143,20 +165,24 @@ GEM rackup (2.1.0) rack (>= 3) webrick (~> 1.8) - rails (7.1.1) - actioncable (= 7.1.1) - actionmailbox (= 7.1.1) - actionmailer (= 7.1.1) - actionpack (= 7.1.1) - actiontext (= 7.1.1) - actionview (= 7.1.1) - activejob (= 7.1.1) - activemodel (= 7.1.1) - activerecord (= 7.1.1) - activestorage (= 7.1.1) - activesupport (= 7.1.1) + rails (7.1.2) + actioncable (= 7.1.2) + actionmailbox (= 7.1.2) + actionmailer (= 7.1.2) + actionpack (= 7.1.2) + actiontext (= 7.1.2) + actionview (= 7.1.2) + activejob (= 7.1.2) + activemodel (= 7.1.2) + activerecord (= 7.1.2) + activestorage (= 7.1.2) + activesupport (= 7.1.2) bundler (>= 1.15.0) - railties (= 7.1.1) + railties (= 7.1.2) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -164,24 +190,40 @@ GEM rails-html-sanitizer (1.6.0) loofah (~> 2.21) nokogiri (~> 1.14) - railties (7.1.1) - actionpack (= 7.1.1) - activesupport (= 7.1.1) + railties (7.1.2) + actionpack (= 7.1.2) + activesupport (= 7.1.2) irb rackup (>= 1.0.0) rake (>= 12.2) thor (~> 1.0, >= 1.2.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.0.6) - rdoc (6.5.0) + rake (13.1.0) + rdoc (6.6.0) psych (>= 4.0.0) regexp_parser (2.8.2) - reline (0.3.9) + reline (0.4.0) io-console (~> 0.5) rexml (3.2.6) - rubocop (1.57.1) - base64 (~> 0.1.1) + rspec-core (3.12.2) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.6) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-rails (6.0.3) + actionpack (>= 6.1) + activesupport (>= 6.1) + railties (>= 6.1) + rspec-core (~> 3.12) + rspec-expectations (~> 3.12) + rspec-mocks (~> 3.12) + rspec-support (~> 3.12) + rspec-support (3.12.1) + rubocop (1.57.2) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -192,9 +234,11 @@ GEM rubocop-ast (>= 1.28.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.29.0) + rubocop-ast (1.30.0) parser (>= 3.2.1.0) ruby-progressbar (1.13.0) + ruby-vips (2.1.4) + ffi (~> 1.12) ruby2_keywords (0.0.5) sprockets (4.2.1) concurrent-ruby (~> 1.0) @@ -203,9 +247,9 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - stringio (3.0.8) + stringio (3.0.9) thor (1.3.0) - timeout (0.4.0) + timeout (0.4.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) @@ -220,10 +264,17 @@ PLATFORMS DEPENDENCIES activejob-web! + brakeman + bundler-audit + factory_bot_rails + image_processing (>= 1.2) + overcommit (~> 0.60.0) pg puma + rails-controller-testing (~> 1.0, >= 1.0.5) + rspec-rails rubocop sprockets-rails BUNDLED WITH - 2.4.19 + 2.4.21 diff --git a/README.md b/README.md index 9ff0d2d..0edd30c 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,116 @@ Or install it yourself as: $ gem install activejob-web ``` + +## Configuring Activejob Web approvers and executors + +By default, Activejob Web uses the `User` class as the model for job approvers and executors. If you need to customize these classes, you can do so in the initializer file located at `config/initializers/activejob_web.rb`. + +For example, if your model to configure is 'Author', you can set the job approvers and executors classes as follows: + +```ruby +# config/initializers/activejob_web.rb + +Activejob::Web.job_approvers_class = 'Author' +Activejob::Web.job_executors_class = 'Author' +``` +where, `job_approvers_class` and `job_executors_class` were defined as `mattr_accessors` in Activejob Web gem. + +## Configuration for Authentication +The ActivejobWeb Gem integrates with the authentication system used in your Rails application. To set up authentication, create a helper file named `authentication_helper.rb` with the `my_app_current_user` method: + +```ruby +# app/helpers/user_helper.rb + +module AuthenticationHelper + def my_app_current_user + # Specify the current user here + User.find(current_user.id) + end +end +``` +In the `my_app_current_user` method, specify the logic to fetch the current user for authentication. Replace the placeholder `User.find(current_user.id)` with the actual code that retrieves the current user. + +Make sure that your application controller includes the helper file named `ActivejobWeb::JobsHelper` and **helper_method** `activejob_web_current_user` as shown below, +if not included please include the helper file and helper_method for the authentication related configurations. + +```ruby +# app/controllers/application_controller.rb + +class ApplicationController < ActionController::Base + include ActivejobWeb::JobsHelper + helper_method :activejob_web_current_user +end +``` + +## Enabling Routes for specific users to edit the Jobs +By enabling this route, The specified super admin users will only have permission to edit the jobs. The implementation involves utilizing `route constraints` and a custom `lambda function` to restrict access based on a `super admin scope`. +Assuming that your model to configure is 'User'. +### 1. Define a scope named `super_admin_users` in the `User` model +Specify the condition in the scope that it should return the users with the permission for the edit page of jobs. The example code was shown below the custom lambda. +### 2. Define a Custom Lambda Method + +In your `User` model or the model you defined for approvers/executors, define a method named `allow_admin_access?` that represents the condition for user access. This lambda function will be used as a route constraint. + +```ruby +# app/models/user.rb + +class User < ApplicationRecord + # Your existing user model code + + # Define a scope for super admins here + scope :super_admin_users, -> { User.all.limit(10) } + + # Your other scopes and methods... + + # Define a custom lambda inside a method for route constraint + def self.allow_admin_access? + lambda do |_request| + super_admin_users.include?(current_user) + # Your custom logic to determine user access goes here + end + end +end +``` +If the condition specified in the `allow_admin_access?` returns `true` the current user can able to edit the jobs. + +### 3. Route customization +In Activejob Web, you have the flexibility to customize routes based on specific user requirements. By default, every user is allowed to edit jobs, but you can customize the routes to be accessible only by specific users. + +For that, first you have to specify the scope and lambda for the `User` or the required model as mentioned in the previous points.Then, start by configuring the `enable_custom_routes` option in the initializer file `activejob_web.rb`. Set it to `true` as shown below: + +```ruby +# config/initializers/activejob_web.rb + +Rails.application.config.enable_custom_routes = true +``` +Your routes for jobs edit will look like this +```ruby +if Rails.application.config.enable_custom_routes == true + get 'activejob_web/jobs/:id/edit', to: 'activejob_web/jobs#edit', + constraints: UserRoleConstraint.new(Activejob::Web.job_approvers_class.constantize.allow_admin_access?), + as: 'edit_activejob_web_job' + else + get 'activejob_web/jobs/:id/edit', to: 'activejob_web/jobs#edit' + end +``` +In this route, you have to customize the method name `allow_admin_access?` if you are using a different name for the method. + +The edit jobs route uses a constraint file named `user_role_constraint.rb` for processing the lambda you define in the `allow_admin_access?` method as shown below, +```ruby +# app/constraints/user_role_constraint.rb + +class UserRoleConstraint + def initialize(lambda_function) + @lambda_function = lambda_function + end + + def matches?(request) + # Call the lambda function and pass the request as an argument + @lambda_function.call(request) + end +end +``` ## Contributing Contribution directions go here. diff --git a/Rakefile b/Rakefile index 34bcc14..5a624a1 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'bundler/setup' APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__) diff --git a/activejob-web.gemspec b/activejob-web.gemspec index 2db4660..cdac4d7 100644 --- a/activejob-web.gemspec +++ b/activejob-web.gemspec @@ -1,27 +1,28 @@ -require_relative 'lib/activejob/web/version' +require_relative "lib/activejob/web/version" Gem::Specification.new do |spec| - spec.name = 'activejob-web' + spec.name = "activejob-web" spec.version = Activejob::Web::VERSION - spec.authors = %w[gowtham mohammednazeer] - spec.email = %w[gowtham.kuppusamy@mallow-tech.com mohammednazeer@mallow-tech.com] - spec.homepage = 'TODO' - spec.summary = 'TODO: Summary of Activejob::Web.' - spec.description = 'TODO: Description of Activejob::Web.' - spec.license = 'MIT' + spec.authors = ["mohammednazeer"] + spec.email = ["mohammednazeer@mallow-tech.com"] + spec.homepage = 'https://example.com' + spec.summary = "Summary of Activejob::Web." + spec.description = "Description of Activejob::Web." + spec.license = "MIT" # Prevent pushing this gem to RubyGems.org. To allow pushes either set the "allowed_push_host" # to allow pushing to a single host or delete this section to allow pushing to any host. - spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'" + spec.metadata["allowed_push_host"] = 'http://mygemserver.com' - spec.metadata['homepage_uri'] = spec.homepage - spec.metadata['source_code_uri'] = "TODO: Put your gem's public repo URL here." - spec.metadata['changelog_uri'] = "TODO: Put your gem's CHANGELOG.md URL here." + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = 'https://example.com' + spec.metadata["changelog_uri"] = 'https://example.com' spec.files = Dir.chdir(File.expand_path(__dir__)) do - Dir['{app,config,db,lib}/**/*', 'MIT-LICENSE', 'Rakefile', 'README.md'] + Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] end - spec.required_ruby_version = '>= 3.2.2' - spec.add_dependency 'rails', '>= 7.0.8' + spec.add_dependency "rails", ">= 7.0.8" + spec.add_development_dependency 'rspec-rails' + spec.add_development_dependency "factory_bot_rails" end diff --git a/app/assets/images/activejob/web/sample.png b/app/assets/images/activejob/web/sample.png new file mode 100644 index 0000000..3b5aaf2 Binary files /dev/null and b/app/assets/images/activejob/web/sample.png differ diff --git a/app/constraints/user_role_constraint.rb b/app/constraints/user_role_constraint.rb new file mode 100644 index 0000000..df5731b --- /dev/null +++ b/app/constraints/user_role_constraint.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class UserRoleConstraint + def initialize(lambda_function) + @lambda_function = lambda_function + end + + def matches?(request) + # Call the lambda function and pass the request as an argument + @lambda_function.call(request) + end +end diff --git a/app/controllers/activejob_web/application_controller.rb b/app/controllers/activejob_web/application_controller.rb new file mode 100644 index 0000000..5181c45 --- /dev/null +++ b/app/controllers/activejob_web/application_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module ActivejobWeb + class ApplicationController < ActionController::Base + include ActivejobWeb::JobsHelper + helper_method :activejob_web_current_user + protect_from_forgery with: :exception + end +end diff --git a/app/controllers/activejob_web/job_approval_requests_controller.rb b/app/controllers/activejob_web/job_approval_requests_controller.rb new file mode 100644 index 0000000..39dca6f --- /dev/null +++ b/app/controllers/activejob_web/job_approval_requests_controller.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module ActivejobWeb + class JobApprovalRequestsController < ApplicationController + before_action :set_job_and_job_execution + before_action :set_approval_request, only: %i[action update] + + def index + @job_approval_requests = ActivejobWeb::JobApprovalRequest.where(job_execution_id: params[:job_execution_id], + response: nil).includes(job_execution: %i[job user]) + end + + def action; end + + def update + return unless @job_approval_request.update(job_approval_request_params) + + redirect_to activejob_web_job_job_execution_job_approval_requests_path(@job, @job_execution), + notice: 'Job approval request updated successfully.' + end + + private + + def set_approval_request + @job_approval_request = ActivejobWeb::JobApprovalRequest.find(params[:id]) + end + + def set_job_and_job_execution + @job = ActivejobWeb::Job.find(params[:job_id]) + @job_execution = ActivejobWeb::JobExecution.find(params[:job_execution_id]) + end + + def job_approval_request_params + params.require(:activejob_web_job_approval_request).permit(:response, :approver_comments) + end + end +end diff --git a/app/controllers/activejob_web/job_executions_controller.rb b/app/controllers/activejob_web/job_executions_controller.rb new file mode 100644 index 0000000..4b62fb3 --- /dev/null +++ b/app/controllers/activejob_web/job_executions_controller.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module ActivejobWeb + class JobExecutionsController < ApplicationController + before_action :set_job_id + def index + @job_executions = ActivejobWeb::JobExecution.where(job_id: params[:job_id]) + @job_execution = @job.job_executions.new + end + + def edit + @job_execution = @job.job_executions.find(params[:id]) + end + + def update + @job_execution = ActivejobWeb::JobExecution.find(params[:id]) + if @job_execution.update(job_execution_params) + redirect_to activejob_web_job_job_execution_path(@job), notice: 'Job execution was successfully updated.' + else + render :edit + end + end + + def show + @job_execution = ActivejobWeb::JobExecution.find(params[:id]) + end + + def create + @job_execution = @job.job_executions.new(job_execution_params) + @job_execution.requestor_id = activejob_web_current_user.id + if @job_execution.save + flash[:notice] = 'Job execution created successfully.' + redirect_to activejob_web_job_job_executions_path(@job) + else + @job_executions = ActivejobWeb::JobExecution.where(job_id: params[:job_id]) + render :index + end + end + end +end + + private + +def job_execution_params + params.require(:activejob_web_job_execution).permit(:requestor_comments, :status, :job_id, :auto_execute_on_approval) +end + +def set_job_id + @job = ActivejobWeb::Job.find(params[:job_id]) +end diff --git a/app/controllers/activejob_web/jobs_controller.rb b/app/controllers/activejob_web/jobs_controller.rb new file mode 100644 index 0000000..5c249d0 --- /dev/null +++ b/app/controllers/activejob_web/jobs_controller.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActivejobWeb + class JobsController < ApplicationController + before_action :set_job, only: %i[show edit update] + + def index + @jobs = ActivejobWeb::Job.includes(:executors).where(activejob_web_job_executors: { executor_id: activejob_web_current_user.id }) + end + + def show + if @job.executors.include?(activejob_web_current_user) + render :show + else + redirect_to root_path + flash[:notice] = 'You are not authorized to perform this action.' + end + end + + def edit; end + + def update + @job.approver_ids = job_params[:approver_ids] + @job.executor_ids = job_params[:executor_ids] + if @job.save + redirect_to activejob_web_job_path(@job) + flash[:notice] = 'Job was successfully updated.' + else + render :edit + end + end + + def download_pdf + if @job.template_file.attached? + send_file @job.template_file.download + else + flash[:error] = 'Template file not found.' + redirect_to @job + end + end + + private + + def set_job + @job = ActivejobWeb::Job.find(params[:id]) + end + + def job_params + params.require(:activejob_web_job).permit(approver_ids: [], executor_ids: []) + end + end +end diff --git a/app/helpers/activejob_web/job_approval_requests_helper.rb b/app/helpers/activejob_web/job_approval_requests_helper.rb new file mode 100644 index 0000000..0db48b0 --- /dev/null +++ b/app/helpers/activejob_web/job_approval_requests_helper.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module ActivejobWeb + module JobApprovalRequestsHelper + end +end diff --git a/app/helpers/activejob_web/job_executions_helper.rb b/app/helpers/activejob_web/job_executions_helper.rb new file mode 100644 index 0000000..954a4a8 --- /dev/null +++ b/app/helpers/activejob_web/job_executions_helper.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module ActivejobWeb + module JobExecutionsHelper + end +end diff --git a/app/helpers/activejob_web/jobs_helper.rb b/app/helpers/activejob_web/jobs_helper.rb new file mode 100644 index 0000000..f4bee23 --- /dev/null +++ b/app/helpers/activejob_web/jobs_helper.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module ActivejobWeb + module JobsHelper + include AuthenticationHelper + + def activejob_web_current_user + my_app_current_user + end + end +end diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 0000000..d394c3d --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/app/jobs/auto_enqueue_job.rb b/app/jobs/auto_enqueue_job.rb new file mode 100644 index 0000000..6a3b66f --- /dev/null +++ b/app/jobs/auto_enqueue_job.rb @@ -0,0 +1,7 @@ +class AutoEnqueueJob < ApplicationJob + queue_as :default + + def perform(job_id,input_arguments) + # puts "Processing AutoEnqueueJob for JobExecution ID: #{job_execution_id}, Job ID: #{job_id}, Input Arguments: #{input_arguments}" + end +end diff --git a/app/models/activejob_web.rb b/app/models/activejob_web.rb new file mode 100644 index 0000000..3ff9a03 --- /dev/null +++ b/app/models/activejob_web.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module ActivejobWeb + def self.table_name_prefix + 'activejob_web_' + end +end diff --git a/app/models/activejob_web/job.rb b/app/models/activejob_web/job.rb new file mode 100644 index 0000000..ece50db --- /dev/null +++ b/app/models/activejob_web/job.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module ActivejobWeb + class Job < ApplicationRecord + self.primary_key = 'id' + + # == Associations ================================================================================================== + has_and_belongs_to_many :approvers, class_name: Activejob::Web.job_approvers_class.to_s, join_table: 'activejob_web_job_approvers', + association_foreign_key: 'approver_id' + has_and_belongs_to_many :executors, class_name: Activejob::Web.job_executors_class.to_s, join_table: 'activejob_web_job_executors', + association_foreign_key: 'executor_id' + has_one_attached :template_file + has_many :job_executions + + # == Validations =================================================================================================== + validates :title, presence: true, length: { maximum: 255 } + validates :description, presence: true, length: { maximum: 1000 } + + # == Callbacks ===================================================================================================== + after_initialize :set_default_queue + + private + + # Default value for queue + def set_default_queue + self.queue ||= 'default' # Set your desired default value for the queue attribute + end + end +end diff --git a/app/models/activejob_web/job_approval_request.rb b/app/models/activejob_web/job_approval_request.rb new file mode 100644 index 0000000..a0dbc30 --- /dev/null +++ b/app/models/activejob_web/job_approval_request.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module ActivejobWeb + class JobApprovalRequest < ApplicationRecord + belongs_to :approver, class_name: Activejob::Web.job_approvers_class.to_s, foreign_key: 'approver_id' + belongs_to :job_execution, class_name: 'ActivejobWeb::JobExecution', foreign_key: 'job_execution_id' + after_update :update_job_execution_status + def update_job_execution_status + if self.response == 1 + job_execution.enqueue_job_if_approved + end + end + end +end diff --git a/app/models/activejob_web/job_execution.rb b/app/models/activejob_web/job_execution.rb new file mode 100644 index 0000000..d04be04 --- /dev/null +++ b/app/models/activejob_web/job_execution.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module ActivejobWeb + class JobExecution < ApplicationRecord + enum status: { + requested: 0, + approved: 1, + rejected: 2, + executed: 3, + cancelled: 4, + succeeded: 5, + failed: 6 + } + + validates :requestor_comments, presence: true + + after_initialize :set_default_status + after_save :send_job_approval_request + + belongs_to :job, class_name: 'ActivejobWeb::Job', foreign_key: 'job_id' + has_many :job_approval_requests + belongs_to :user, class_name: 'User', foreign_key: 'requestor_id' + def response_count + job_approval_requests.where(response: 1).count + end + def enqueue_job_if_approved + if auto_execute_on_approval? && enough_approvals? + # needs to enqueue the job with the job id and input arguments + AutoEnqueueJob.perform_later(job.id, job.input_arguments) + end + end + + private + def set_default_status + self.status ||= :requested + end + def send_job_approval_request + job.approvers.each do |approver| + job_approval_requests.create(job_execution_id: id, approver_id: approver.id) + end + end + + def enough_approvals? + response_count >= job.minimum_approvals_required + end + end +end \ No newline at end of file diff --git a/app/views/activejob_web/job_approval_requests/action.html.erb b/app/views/activejob_web/job_approval_requests/action.html.erb new file mode 100644 index 0000000..612a056 --- /dev/null +++ b/app/views/activejob_web/job_approval_requests/action.html.erb @@ -0,0 +1,29 @@ +<%= form_for [@job, @job_execution, @job_approval_request], url: activejob_web_job_job_execution_job_approval_request_path(@job, @job_execution, @job_approval_request) do |form| %> + <% if @job_approval_request.errors.any? %> + <% @job_approval_request.errors.full_messages.each do |message| %> +

<%= message %>

+ <% end %> + <% end %> +

Job Approval Request: <%= @job_approval_request.job_execution.job.title %>

+

Job Title: <%= @job_approval_request.job_execution.job.title %>

+

Job Description: <%= @job_approval_request.job_execution.job.description %>

+

Minimum Approvals Required: <%= @job_approval_request.job_execution.job.minimum_approvals_required %>

+

Priority: <%= @job_approval_request.job_execution.job.priority %>

+

Requestor Name: <%= @job_approval_request.job_execution.user.name %>

+

Requestor email: <%= @job_approval_request.job_execution.user.email %>

+ +
+ <%= form.label :response, 'Approve' %> + <%= form.radio_button :response, 1, checked: @job_approval_request.response == 1 %> + + <%= form.label :response, 'Decline' %> + <%= form.radio_button :response, 0, checked: @job_approval_request.response == 0 %> +

+ +
+ <%= form.label :approver_comments %> + <%= form.text_field :approver_comments %> +

+ +
<%= form.submit 'submit' %>
+<% end %> \ No newline at end of file diff --git a/app/views/activejob_web/job_approval_requests/index.html.erb b/app/views/activejob_web/job_approval_requests/index.html.erb new file mode 100644 index 0000000..bf67fe9 --- /dev/null +++ b/app/views/activejob_web/job_approval_requests/index.html.erb @@ -0,0 +1,24 @@ +

Jobs Waiting for Approval Request

+<% if flash[:notice] %> +
+ <%= flash[:notice] %> +
+<% end %> + + + + + + + + + + <% @job_approval_requests.each do |approval_request| %> + + + + + + <% end %> + +
Job titleRequestor nameActions
<%= approval_request.job_execution.job.title %><%= approval_request.job_execution.user.name %><%= button_to 'Approve/Decline', action_activejob_web_job_job_execution_job_approval_request_path(approval_request.job_execution.job, approval_request.job_execution, approval_request), method: :get %>
diff --git a/app/views/activejob_web/job_executions/_job_execution_form.html.erb b/app/views/activejob_web/job_executions/_job_execution_form.html.erb new file mode 100644 index 0000000..ae21939 --- /dev/null +++ b/app/views/activejob_web/job_executions/_job_execution_form.html.erb @@ -0,0 +1,18 @@ +<%= form_for [@job, @job_execution] ,url: url do |form| %> + <% if @job_execution.errors.any? %> +
+

<%= pluralize(@job_execution.errors.count, "error") %> prohibited this job_execution from being saved:

+ + +
+ <%= form.select :status, ActivejobWeb::JobExecution.statuses.keys %> + <%= form.text_field :requestor_comments, placeholder: "Requestor Comments" %> + <%= form.label :auto_execute_on_approval %> + <%= form.check_box :auto_execute_on_approval %> + <%= form.submit %> +<% end %> diff --git a/app/views/activejob_web/job_executions/edit.html.erb b/app/views/activejob_web/job_executions/edit.html.erb new file mode 100644 index 0000000..5c00755 --- /dev/null +++ b/app/views/activejob_web/job_executions/edit.html.erb @@ -0,0 +1,2 @@ +

Edit Job Execution

+<%= render partial: 'job_execution_form',locals: {url:activejob_web_job_job_execution_path(@job,@job_execution)} %> diff --git a/app/views/activejob_web/job_executions/index.html.erb b/app/views/activejob_web/job_executions/index.html.erb new file mode 100644 index 0000000..c9fa60b --- /dev/null +++ b/app/views/activejob_web/job_executions/index.html.erb @@ -0,0 +1,26 @@ +

Job Executions

+<% if flash[:notice].present? %> +
+ <%= flash[:notice] %> +
+<% end %> +<%= render partial: 'job_execution_form',locals: {url:activejob_web_job_job_executions_path(@job)}%> + + + + + + + + + <% @job_executions.each do |job_execution| %> + + + + + + + + + <% end %> +
IDStatusRequestor CommentsAuto Execute On ApprovalActions
<%= job_execution.id %><%= job_execution.status %><%= job_execution.requestor_comments %><%= job_execution.auto_execute_on_approval %><%= link_to 'Show', activejob_web_job_job_execution_path(@job,job_execution) %><%= link_to 'Back', activejob_web_jobs_path %>
diff --git a/app/views/activejob_web/job_executions/show.html.erb b/app/views/activejob_web/job_executions/show.html.erb new file mode 100644 index 0000000..8ffd116 --- /dev/null +++ b/app/views/activejob_web/job_executions/show.html.erb @@ -0,0 +1,15 @@ +

Job Execution Details

+ +

ID: <%= @job_execution.id %>

+

Status: <%= @job_execution.status %>

+

Requestor Comments: <%= @job_execution.requestor_comments %>

+

Auto_execute_on_approval: <%= @job_execution.auto_execute_on_approval %>

+

Reason for Failure: <%= @job_execution.reason_for_failure %>

+

Arguments:<%= @job_execution.arguments %>

+

Auto Execute on Approval: <%= @job_execution.auto_execute_on_approval %>

+

Run At: <%= @job_execution.run_at %>

+

Execution Started At: <%= @job_execution.execution_started_at %>

+ +<%= link_to 'Edit', edit_activejob_web_job_job_execution_path(@job,@job_execution) %> | +<%= link_to 'Back', activejob_web_job_job_executions_path %> +<%= link_to 'Job_approval_requests',activejob_web_job_job_execution_job_approval_requests_path(@job, @job_execution, @job_approval_requests) %> diff --git a/app/views/activejob_web/jobs/edit.html.erb b/app/views/activejob_web/jobs/edit.html.erb new file mode 100644 index 0000000..d17345f --- /dev/null +++ b/app/views/activejob_web/jobs/edit.html.erb @@ -0,0 +1,47 @@ +

Job: <%= @job.title %>

+ +<%= form_with model: @job do |form| %> + <% if @job.errors.any? %> + <% @job.errors.full_messages.each do |message| %> +

<%= message %>

+ <% end %> + <% end %> + +
+

Assigned approvers for <%= @job.title %>

+ +
+ +
+

Assigned executors for <%= @job.title %>

+ +
+ + <% users = Activejob::Web.job_approvers_class.to_s.constantize.all %> +
+ <%= form.label :approvers %> + <%= form.collection_select(:approver_ids, users, :id, :name, { multiple: true }) %> +

+ +
+ <%= form.label :executors %> + <%= form.collection_select(:executor_ids, users, :id, :name, {}, { multiple: true }) %> +

+
<%= form.submit %>
+<% end %> diff --git a/app/views/activejob_web/jobs/index.html.erb b/app/views/activejob_web/jobs/index.html.erb new file mode 100644 index 0000000..a2c7645 --- /dev/null +++ b/app/views/activejob_web/jobs/index.html.erb @@ -0,0 +1,29 @@ +<% if flash[:notice] %> +
+ <%= flash[:notice] %> +
+<% end %> +

Active Job Web Jobs

+ + + + + + + + + + + + + <% @jobs.each do |job| %> + + + + + + + + <% end %> + +
TitleDescriptionIDqueueActions
<%= job.title %><%= job.description %><%= job.id %><%= job.queue %><%= link_to 'Show', activejob_web_job_path(job) %>
diff --git a/app/views/activejob_web/jobs/show.html.erb b/app/views/activejob_web/jobs/show.html.erb new file mode 100644 index 0000000..3aa0658 --- /dev/null +++ b/app/views/activejob_web/jobs/show.html.erb @@ -0,0 +1,49 @@ +<% if flash[:notice] %> +
+ <%= flash[:notice] %> +
+<% end %> +

<%= @job.title %>

+ +

Description: <%= @job.description %>

+

Input Arguments: <%= @job.input_arguments %>

+

Max Run Time: <%= @job.max_run_time %>

+

Minimum Approvals Required: <%= @job.minimum_approvals_required %>

+

Priority: <%= @job.priority %>

+

Queue: <%= @job.queue %>

+<% if @job.template_file.attached? %> + <%= link_to "Download PDF", rails_blob_path(@job.template_file, disposition: "attachment"), class: "btn btn-primary" %> +<% else %> +

no template available

+<% end %> +<% if Activejob::Web.job_approvers_class.constantize.super_admin_users.include?(activejob_web_current_user) %> +
+

Assigned approvers for <%= @job.title %>

+ +
+ +
+

Assigned executors for <%= @job.title %>

+ +
+

<%= link_to 'Edit Job', edit_activejob_web_job_path(@job) %>

+<% end %> +

<%= link_to 'Back', activejob_web_jobs_path %>

+ +

<%= link_to 'Job Executions', activejob_web_job_job_executions_path(@job, @job_executions) %>

diff --git a/bin/rails b/bin/rails index dd5828f..d8e745b 100755 --- a/bin/rails +++ b/bin/rails @@ -1,4 +1,6 @@ #!/usr/bin/env ruby +# frozen_string_literal: true + # This command will automatically be run when you run "rails" with Rails gems # installed from the root of your application. diff --git a/config/initializers/activejob_web.rb b/config/initializers/activejob_web.rb new file mode 100644 index 0000000..1ab1b8e --- /dev/null +++ b/config/initializers/activejob_web.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# Activejob::Web.job_approvers_class = 'Author' +# Activejob::Web.job_executors_class = 'Author' + +Rails.application.config.enable_custom_routes = false diff --git a/config/routes.rb b/config/routes.rb index 643baa7..a8b205b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,27 @@ +# frozen_string_literal: true + Rails.application.routes.draw do - # Defines the root path route ("/") + root 'activejob_web/jobs#index' + + namespace :activejob_web do + resources :jobs, only: %i[index show update] do + resources :job_executions do + resources :job_approval_requests do + member do + get :action + end + end + end + member do + get :download_pdf + end + end + end + if Rails.application.config.enable_custom_routes == true + get 'activejob_web/jobs/:id/edit', to: 'activejob_web/jobs#edit', + constraints: UserRoleConstraint.new(Activejob::Web.job_approvers_class.constantize.allow_admin_access?), + as: 'edit_activejob_web_job' + else + get 'activejob_web/jobs/:id/edit', to: 'activejob_web/jobs#edit', as: 'edit_activejob_web_job' + end end diff --git a/db/migrate/20231024051840_create_jobs.rb b/db/migrate/20231024051840_create_jobs.rb new file mode 100644 index 0000000..113b771 --- /dev/null +++ b/db/migrate/20231024051840_create_jobs.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class CreateJobs < ActiveRecord::Migration[7.1] + def change + create_table :activejob_web_jobs, id: false do |t| + t.uuid :id, default: -> { 'gen_random_uuid()' }, null: false + t.string :title + t.string :description + t.json :input_arguments + t.integer :max_run_time + t.integer :minimum_approvals_required + t.integer :priority + t.integer :queue + + t.timestamps + end + end +end diff --git a/db/migrate/20231030101923_create_join_table_job_approvers.rb b/db/migrate/20231030101923_create_join_table_job_approvers.rb new file mode 100644 index 0000000..47f2642 --- /dev/null +++ b/db/migrate/20231030101923_create_join_table_job_approvers.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class CreateJoinTableJobApprovers < ActiveRecord::Migration[7.1] + def change + create_table :activejob_web_job_approvers, id: false do |t| + t.column :job_id, :uuid + t.column :approver_id, :integer + end + end +end diff --git a/db/migrate/20231030102259_create_join_table_job_executors.rb b/db/migrate/20231030102259_create_join_table_job_executors.rb new file mode 100644 index 0000000..f3da157 --- /dev/null +++ b/db/migrate/20231030102259_create_join_table_job_executors.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class CreateJoinTableJobExecutors < ActiveRecord::Migration[7.1] + def change + create_table :activejob_web_job_executors, id: false do |t| + t.column :job_id, :uuid + t.column :executor_id, :integer + end + end +end diff --git a/db/migrate/20231030145254_create_active_storage_tables.active_storage.rb b/db/migrate/20231030145254_create_active_storage_tables.active_storage.rb new file mode 100644 index 0000000..4dfc381 --- /dev/null +++ b/db/migrate/20231030145254_create_active_storage_tables.active_storage.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +# This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[7.0] + def change + primary_key_type, foreign_key_type = primary_and_foreign_key_types + unless table_exists?(:active_storage_blobs) + create_table :active_storage_blobs, id: primary_key_type do |t| + t.string :key, null: false + t.string :filename, null: false + t.string :content_type + t.text :metadata + t.string :service_name, null: false + t.bigint :byte_size, null: false + t.string :checksum + + if connection.supports_datetime_with_precision? + t.datetime :created_at, precision: 6, null: false + else + t.datetime :created_at, null: false + end + + t.index [:key], unique: true + end + end + # Use Active Record's configured type for primary and foreign keys + + unless table_exists?(:active_storage_attachments) + create_table :active_storage_attachments, id: primary_key_type do |t| + t.string :name, null: false + t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type + t.references :blob, null: false, type: foreign_key_type + + if connection.supports_datetime_with_precision? + t.datetime :created_at, precision: 6, null: false + else + t.datetime :created_at, null: false + end + + t.index %i[record_type record_id name blob_id], name: :index_active_storage_attachments_uniqueness, unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end + + return if table_exists?(:active_storage_variant_records) + + create_table :active_storage_variant_records, id: primary_key_type do |t| + t.belongs_to :blob, null: false, index: false, type: foreign_key_type + t.string :variation_digest, null: false + + t.index %i[blob_id variation_digest], name: :index_active_storage_variant_records_uniqueness, unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end + + private + + def primary_and_foreign_key_types + config = Rails.configuration.generators + setting = config.options[config.orm][:primary_key_type] + primary_key_type = setting || :primary_key + foreign_key_type = setting || :bigint + [primary_key_type, foreign_key_type] + end +end diff --git a/db/migrate/20231106105039_create_activejob_web_job_executions.rb b/db/migrate/20231106105039_create_activejob_web_job_executions.rb new file mode 100644 index 0000000..c731f90 --- /dev/null +++ b/db/migrate/20231106105039_create_activejob_web_job_executions.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class CreateActivejobWebJobExecutions < ActiveRecord::Migration[7.1] + def change + create_table :activejob_web_job_executions do |t| + t.integer :requestor_id + t.uuid :job_id + t.string :requestor_comments + t.json :arguments + t.integer :status + t.string :reason_for_failure + t.boolean :auto_execute_on_approval + t.timestamp :run_at + t.timestamp :execution_started_at + t.timestamps + end + end +end diff --git a/db/migrate/20231122042337_create_job_approval_requests.rb b/db/migrate/20231122042337_create_job_approval_requests.rb new file mode 100644 index 0000000..cf7503c --- /dev/null +++ b/db/migrate/20231122042337_create_job_approval_requests.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class CreateJobApprovalRequests < ActiveRecord::Migration[7.1] + def change + create_table :activejob_web_job_approval_requests do |t| + t.integer :job_execution_id + t.integer :approver_id + t.integer :response + t.string :approver_comments + + t.timestamps + end + end +end diff --git a/lib/activejob/web.rb b/lib/activejob/web.rb index cb94080..209c764 100644 --- a/lib/activejob/web.rb +++ b/lib/activejob/web.rb @@ -1,8 +1,11 @@ +# frozen_string_literal: true + require 'activejob/web/version' require 'activejob/web/engine' module Activejob module Web - # Your code goes here... + mattr_accessor :job_approvers_class, default: 'User' + mattr_accessor :job_executors_class, default: 'User' end end diff --git a/lib/activejob/web/engine.rb b/lib/activejob/web/engine.rb index 6cab492..f08d16a 100644 --- a/lib/activejob/web/engine.rb +++ b/lib/activejob/web/engine.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Activejob module Web class Engine < ::Rails::Engine diff --git a/lib/activejob/web/version.rb b/lib/activejob/web/version.rb index f5c83eb..0eeffe7 100644 --- a/lib/activejob/web/version.rb +++ b/lib/activejob/web/version.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + module Activejob module Web - VERSION = '0.1.0'.freeze + VERSION = '0.1.0' end end diff --git a/lib/tasks/activejob/web_tasks.rake b/lib/tasks/activejob/web_tasks.rake index 0f60b46..1996cc2 100644 --- a/lib/tasks/activejob/web_tasks.rake +++ b/lib/tasks/activejob/web_tasks.rake @@ -1,3 +1,4 @@ +# frozen_string_literal: true # desc "Explaining what the task does" # task :activejob_web do # # Task goes here diff --git a/spec/factories/job_approval_requests.rb b/spec/factories/job_approval_requests.rb new file mode 100644 index 0000000..e74b3af --- /dev/null +++ b/spec/factories/job_approval_requests.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +# spec/factories/job_approval_requests.rb +FactoryBot.define do + factory :job_approval_request, class: ActivejobWeb::JobApprovalRequest do + job_execution_id { 1 } + approver_id { 1 } + response { 1 } + approver_comments { 'Comments' } + end +end diff --git a/spec/factories/job_executions.rb b/spec/factories/job_executions.rb new file mode 100644 index 0000000..8cc0e47 --- /dev/null +++ b/spec/factories/job_executions.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +# spec/factories/job_executions.rb +FactoryBot.define do + factory :job_execution, class: ActivejobWeb::JobExecution do + requestor_id { 1 } # Adjust as needed + job_id { SecureRandom.uuid } # Generates a random UUID + requestor_comments { 'Requestor comments' } + arguments { { key: 'value' } } # Example JSON structure, adjust as needed + status { 0 } # Example status, adjust as needed + reason_for_failure { 'Reason for failure' } + auto_execute_on_approval { false } + run_at { Time.current } + execution_started_at { Time.current } + end + + factory :valid_job_execution, class: ActivejobWeb::JobExecution do + status { 'requested' } + requestor_comments { 'joy' } + auto_execute_on_approval { true } + end + + factory :invalid_job_execution, class: ActivejobWeb::JobExecution do + # This factory intentionally creates an invalid job execution with an invalid status + status { 'approved' } + auto_execute_on_approval { true } + end +end diff --git a/spec/factories/jobs.rb b/spec/factories/jobs.rb new file mode 100644 index 0000000..b349891 --- /dev/null +++ b/spec/factories/jobs.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :job, class: ActivejobWeb::Job do + title { 'Activejob' } + description { 'Web Gem' } + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb new file mode 100644 index 0000000..9cb3c76 --- /dev/null +++ b/spec/factories/users.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :user, class: User do + email { 'user@gmail.com' } + name { 'user' } + password { 'password@123' } + end + + factory :approver, class: User do + email { 'testapprover@gmail.com' } + name { 'Test approver' } + password { 'password@123' } + end + + factory :approver1, class: User do + email { 'testapprover1@gmail.com' } + name { 'First approver' } + password { 'password@123' } + end + + factory :executor, class: User do + email { 'testexecutor@gmail.com' } + name { 'Test executor' } + password { 'password@123' } + end + + factory :executor1, class: User do + email { 'testexecutor1@gmail.com' } + name { 'First executor' } + password { 'password@123' } + end +end diff --git a/spec/helpers/activejob_web/job_approval_requests_helper_spec.rb b/spec/helpers/activejob_web/job_approval_requests_helper_spec.rb new file mode 100644 index 0000000..8bc88ec --- /dev/null +++ b/spec/helpers/activejob_web/job_approval_requests_helper_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the ActivejobWeb::JobApprovalRequestsHelper. For example: +# +# describe ActivejobWeb::JobApprovalRequestsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe ActivejobWeb::JobApprovalRequestsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/helpers/activejob_web/job_executions_helper_spec.rb b/spec/helpers/activejob_web/job_executions_helper_spec.rb new file mode 100644 index 0000000..89215a3 --- /dev/null +++ b/spec/helpers/activejob_web/job_executions_helper_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the ActivejobWeb::JobExecutionsHelper. For example: +# +# describe ActivejobWeb::JobExecutionsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe ActivejobWeb::JobExecutionsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/helpers/activejob_web/jobs_helper_spec.rb b/spec/helpers/activejob_web/jobs_helper_spec.rb new file mode 100644 index 0000000..563ba52 --- /dev/null +++ b/spec/helpers/activejob_web/jobs_helper_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the ActivejobWeb::JobsHelper. For example: +# +# describe ActivejobWeb::JobsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe ActivejobWeb::JobsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/jobs/auto_enqueue_job_spec.rb b/spec/jobs/auto_enqueue_job_spec.rb new file mode 100644 index 0000000..8c29488 --- /dev/null +++ b/spec/jobs/auto_enqueue_job_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe AutoEnqueueJob, type: :job do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/activejob_web/job_approval_request_spec.rb b/spec/models/activejob_web/job_approval_request_spec.rb new file mode 100644 index 0000000..3e5abd6 --- /dev/null +++ b/spec/models/activejob_web/job_approval_request_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivejobWeb::JobApprovalRequest, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/activejob_web/job_execution_spec.rb b/spec/models/activejob_web/job_execution_spec.rb new file mode 100644 index 0000000..018becc --- /dev/null +++ b/spec/models/activejob_web/job_execution_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivejobWeb::JobExecution, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/activejob_web/job_spec.rb b/spec/models/activejob_web/job_spec.rb new file mode 100644 index 0000000..b83f934 --- /dev/null +++ b/spec/models/activejob_web/job_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivejobWeb::Job, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 0000000..2269bcc --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +# This file is copied to spec/ when you run 'rails generate rspec:install' +require 'spec_helper' +ENV['RAILS_ENV'] ||= 'test' +require File.expand_path('../test/dummy/config/environment', __dir__) +# Prevent database truncation if the environment is production +abort('The Rails environment is running in production mode!') if Rails.env.production? +require 'rspec/rails' +require 'factory_bot_rails' +FactoryBot.definition_file_paths = [File.expand_path('factories', __dir__)] +FactoryBot.find_definitions +# Add additional requires below this line. Rails is not loaded until this point! + +# Requires supporting ruby files with custom matchers and macros, etc, in +# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +# run as spec files by default. This means that files in spec/support that end +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f } + +# Checks for pending migrations and applies them before tests are run. +# If you are not using ActiveRecord, you can remove these lines. +begin + ActiveRecord::Migration.maintain_test_schema! +rescue ActiveRecord::PendingMigrationError => e + puts e.to_s.strip +end +RSpec.configure do |config| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_path = "#{Rails.root}/spec/fixtures" + + config.include FactoryBot::Syntax::Methods + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + # You can uncomment this line to turn off ActiveRecord support entirely. + # config.use_active_record = false + + # RSpec Rails can automatically mix in different behaviours to your tests + # based on their file location, for example enabling you to call `get` and + # `post` in specs under `spec/controllers`. + # + # You can disable this behaviour by removing the line below, and instead + # explicitly tag your specs with their type, e.g.: + # + # RSpec.describe UsersController, type: :controller do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://rspec.info/features/6-0/rspec-rails + config.infer_spec_type_from_file_location! + + # Filter lines from Rails gems in backtraces. + config.filter_rails_from_backtrace! + + config.include FactoryBot::Syntax::Methods + # arbitrary gems may also be filtered via: + # config.filter_gems_from_backtrace("gem name") +end diff --git a/spec/requests/activejob_web/job_approval_requests_spec.rb b/spec/requests/activejob_web/job_approval_requests_spec.rb new file mode 100644 index 0000000..0431479 --- /dev/null +++ b/spec/requests/activejob_web/job_approval_requests_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'ActivejobWeb::JobApprovalRequests', type: :request do + let(:job) { create(:job) } + let(:user) { create(:user) } + let(:job_execution) { create(:job_execution, job_id: job.id, requestor_id: user.id) } + let(:job_approval_request) { create(:job_approval_request, job_execution_id: job_execution.id, approver_id: user.id) } + before do + allow_any_instance_of(ActivejobWeb::JobsHelper).to receive(:activejob_web_current_user).and_return(user) + end + describe 'GET #index' do + it 'renders the index template' do + get activejob_web_job_job_execution_job_approval_requests_path(job.id, job_execution.id) + expect(response).to render_template('index') + expect(response).to have_http_status 200 + end + end + + describe 'GET #action' do + context 'valid' do + it 'renders action page with valid data' do + get action_activejob_web_job_job_execution_job_approval_request_path(job.id, job_execution.id, job_approval_request.id) + expect(response).to render_template('action') + expect(response).to have_http_status 200 + end + end + + context 'invalid' do + it 'should not render action page with invalid data' do + expect do + get action_activejob_web_job_job_execution_job_approval_request_path(SecureRandom.uuid, 1, 1) + end.to raise_error(ActiveRecord::RecordNotFound) + end + end + end + + describe 'PATCH #update' do + context 'valid' do + it 'valid update of job approval request' do + patch activejob_web_job_job_execution_job_approval_request_path(job, job_execution, job_approval_request), + params: { activejob_web_job_approval_request: { response: 0, approver_comments: 'Test comments' } } + job_approval_request.reload + expect(job_approval_request.response).to eq(0) + expect(job_approval_request.approver_comments).to eq('Test comments') + expect(response).to have_http_status 302 + expect(flash[:notice]).to eq('Job approval request updated successfully.') + expect(response).to redirect_to(activejob_web_job_job_execution_job_approval_requests_path(job, job_execution)) + end + end + end +end diff --git a/spec/requests/activejob_web/job_executions_spec.rb b/spec/requests/activejob_web/job_executions_spec.rb new file mode 100644 index 0000000..4ca6ed4 --- /dev/null +++ b/spec/requests/activejob_web/job_executions_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'rails_helper' +RSpec.describe ActivejobWeb::JobExecutionsController, type: :request do + let(:job) { create(:job) } # Assuming you have a Job model + let!(:user) { create(:user) } + let(:execution) { create(:job_execution, job:, requestor_id: user.id) } + before do + allow_any_instance_of(ActivejobWeb::JobsHelper).to receive(:activejob_web_current_user).and_return(user) + end + describe 'GET #index' do + it 'renders the index template' do + get activejob_web_job_job_executions_path(job_id: job.id) + + expect(response).to have_http_status 200 + expect(response).to render_template(:index) + end + end + + describe 'GET #show' do + it 'renders the show template' do + get activejob_web_job_job_execution_path(job_id: job.id, id: execution.id) + expect(response).to have_http_status 200 + expect(response).to render_template(:show) + end + end + + describe 'GET #edit' do + it 'renders the edit template' do + get edit_activejob_web_job_job_execution_path(job, execution) + + expect(response).to have_http_status 200 + expect(response).to render_template(:edit) + end + end + + describe 'POST #create' do + context 'with valid parameters' do + let(:valid_execution_attributes) { attributes_for(:valid_job_execution) } + + it 'creates a new job execution' do + post activejob_web_job_job_executions_path(job), params: { + activejob_web_job_execution: valid_execution_attributes + } + + expect(response).to have_http_status(302) # Redirect status + expect(flash[:notice]).to eq('Job execution created successfully.') + expect(response).to redirect_to(activejob_web_job_job_executions_path(job)) + end + end + + context 'with invalid parameters' do + let(:invalid_execution_attributes) { attributes_for(:invalid_job_execution) } + + it 'renders the index template' do + post activejob_web_job_job_executions_path(job), params: { + activejob_web_job_execution: invalid_execution_attributes + } + + expect(response).to have_http_status(200) # Success status since it renders the index template + expect(response).to render_template(:index) + end + end + end + describe 'PATCH #update' do + context 'with valid parameters' do + it 'updates the job execution and redirects to the show page' do + patch activejob_web_job_job_execution_path(job, execution), + params: { activejob_web_job_execution: { status: 'requested' } } + + expect(response).to have_http_status 302 # Redirect status + expect(flash[:notice]).to eq('Job execution was successfully updated.') + expect(response).to redirect_to(activejob_web_job_job_execution_path(job)) + end + end + end +end diff --git a/spec/requests/activejob_web/jobs_spec.rb b/spec/requests/activejob_web/jobs_spec.rb new file mode 100644 index 0000000..d393a1f --- /dev/null +++ b/spec/requests/activejob_web/jobs_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivejobWeb::JobsController, type: :request do + let(:valid_attributes) { { title: 'Activejob', description: 'Web Gem' } } + let(:job) { create(:job) } + let!(:user) { create(:user) } + before do + allow_any_instance_of(ActivejobWeb::JobsHelper).to receive(:activejob_web_current_user).and_return(user) + end + describe 'GET /index' do + context 'returns a successful response' do + it 'Valid index' do + get activejob_web_jobs_path + expect(response).to render_template('index') + expect(response).to have_http_status 200 + end + end + end + + describe 'GET #show' do + context 'returns a successful response' do + it 'Valid show' do + job.executors << user + get activejob_web_job_path(job.id) + expect(response).to render_template('show') + expect(response).to have_http_status 200 + end + end + end + + describe 'GET #edit' do + context 'valid' do + it 'valid edit' do + get edit_activejob_web_job_path(job.id) + expect(response).to have_http_status 200 + end + end + + context 'invalid' do + it 'invalid edit without parameters' do + expect { get edit_activejob_web_job_path(SecureRandom.uuid) }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end + + describe 'PATCH #update' do + let(:approver) { create(:approver) } + let(:executor) { create(:executor) } + let(:approver1) { create(:approver1) } + let(:executor1) { create(:executor1) } + context 'valid' do + it 'valid update of job with approvers and executors gets assigned' do + patch activejob_web_job_path(job.id), + params: { id: job.id, activejob_web_job: valid_attributes.merge(approver_ids: [approver.id], executor_ids: [executor.id]) } + job.reload + expect(response).to redirect_to(activejob_web_job_path(job)) + expect(job.approvers).to include(approver) + expect(job.executors).to include(executor) + end + + it 'valid update of job with removal of assigned approvers and executors' do + job1 = ActivejobWeb::Job.create(valid_attributes.merge(approver_ids: [approver1.id, approver.id], executor_ids: [executor1.id, executor.id])) + expect(job1.approvers).to include(approver1) + expect(job1.executors).to include(executor1) + patch activejob_web_job_path(job1), + params: { id: job1.id, activejob_web_job: valid_attributes.merge(approver_ids: [approver.id], executor_ids: [executor.id]) } + job1.reload + expect(response).to redirect_to(activejob_web_job_path(job1)) + expect(job1.approvers).not_to include(approver1) + expect(job1.executors).not_to include(executor1) + end + end + + context 'invalid' do + it 'should not update job with invalid approvers and executors' do + expect do + patch activejob_web_job_path(job.id), + params: { id: job.id, activejob_web_job: valid_attributes.merge(approver_ids: ['4'], executor_ids: ['Test']) } + end.to raise_error(ActiveRecord::RecordNotFound) + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..409c64b --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +# This file was generated by the `rails generate rspec:install` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + + # The settings below are suggested to provide a good initial experience + # with RSpec, but feel free to customize to your heart's content. + # # This allows you to limit a spec run to individual examples or groups + # # you care about by tagging them with `:focus` metadata. When nothing + # # is tagged with `:focus`, all examples get run. RSpec also provides + # # aliases for `it`, `describe`, and `context` that include `:focus` + # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + # config.filter_run_when_matching :focus + # + # # Allows RSpec to persist some state between runs in order to support + # # the `--only-failures` and `--next-failure` CLI options. We recommend + # # you configure your source control system to ignore this file. + # config.example_status_persistence_file_path = "spec/examples.txt" + # + # # Limits the available syntax to the non-monkey patched syntax that is + # # recommended. For more details, see: + # # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ + # config.disable_monkey_patching! + # + # # Many RSpec users commonly either run the entire suite or an individual + # # file, and it's useful to allow more verbose output when running an + # # individual spec file. + # if config.files_to_run.one? + # # Use the documentation formatter for detailed output, + # # unless a formatter has already been configured + # # (e.g. via a command-line flag). + # config.default_formatter = "doc" + # end + # + # # Print the 10 slowest examples and example groups at the + # # end of the spec run, to help surface which specs are running + # # particularly slow. + # config.profile_examples = 10 + # + # # Run specs in random order to surface order dependencies. If you find an + # # order dependency and want to debug it, you can fix the order by providing + # # the seed, which is printed after each run. + # # --seed 1234 + # config.order = :random + # + # # Seed global randomization in this process using the `--seed` CLI option. + # # Setting this allows you to use `--seed` to deterministically reproduce + # # test failures related to randomization by passing the same `--seed` value + # # as the one that triggered the failure. + # Kernel.srand config.seed +end diff --git a/test/activejob/web_test.rb b/test/activejob/web_test.rb index 8593d80..98dcd1e 100644 --- a/test/activejob/web_test.rb +++ b/test/activejob/web_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' module Activejob diff --git a/test/controllers/activejob_web/jobs_controller_test.rb b/test/controllers/activejob_web/jobs_controller_test.rb new file mode 100644 index 0000000..f806d64 --- /dev/null +++ b/test/controllers/activejob_web/jobs_controller_test.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'test_helper' + +module ActivejobWeb + class JobsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end + end +end diff --git a/test/controllers/activejob_web_jobs_controller_test.rb b/test/controllers/activejob_web_jobs_controller_test.rb new file mode 100644 index 0000000..8a57acb --- /dev/null +++ b/test/controllers/activejob_web_jobs_controller_test.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'test_helper' + +class ActivejobWebJobsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/dummy/Rakefile b/test/dummy/Rakefile index e85f913..488c551 100644 --- a/test/dummy/Rakefile +++ b/test/dummy/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. diff --git a/test/dummy/app/channels/application_cable/channel.rb b/test/dummy/app/channels/application_cable/channel.rb index d672697..9aec230 100644 --- a/test/dummy/app/channels/application_cable/channel.rb +++ b/test/dummy/app/channels/application_cable/channel.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ApplicationCable class Channel < ActionCable::Channel::Base end diff --git a/test/dummy/app/channels/application_cable/connection.rb b/test/dummy/app/channels/application_cable/connection.rb index 0ff5442..8d6c2a1 100644 --- a/test/dummy/app/channels/application_cable/connection.rb +++ b/test/dummy/app/channels/application_cable/connection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ApplicationCable class Connection < ActionCable::Connection::Base end diff --git a/test/dummy/app/controllers/application_controller.rb b/test/dummy/app/controllers/application_controller.rb index 09705d1..7944f9f 100644 --- a/test/dummy/app/controllers/application_controller.rb +++ b/test/dummy/app/controllers/application_controller.rb @@ -1,2 +1,4 @@ +# frozen_string_literal: true + class ApplicationController < ActionController::Base end diff --git a/test/dummy/app/helpers/application_helper.rb b/test/dummy/app/helpers/application_helper.rb index de6be79..15b06f0 100644 --- a/test/dummy/app/helpers/application_helper.rb +++ b/test/dummy/app/helpers/application_helper.rb @@ -1,2 +1,4 @@ +# frozen_string_literal: true + module ApplicationHelper end diff --git a/test/dummy/app/helpers/authentication_helper.rb b/test/dummy/app/helpers/authentication_helper.rb new file mode 100644 index 0000000..97cbcf1 --- /dev/null +++ b/test/dummy/app/helpers/authentication_helper.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module AuthenticationHelper + def my_app_current_user + # Specify the current user here + current_user + end +end diff --git a/test/dummy/app/jobs/application_job.rb b/test/dummy/app/jobs/application_job.rb index d394c3d..bef3959 100644 --- a/test/dummy/app/jobs/application_job.rb +++ b/test/dummy/app/jobs/application_job.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ApplicationJob < ActiveJob::Base # Automatically retry jobs that encountered a deadlock # retry_on ActiveRecord::Deadlocked diff --git a/test/dummy/app/mailers/application_mailer.rb b/test/dummy/app/mailers/application_mailer.rb index 286b223..d84cb6e 100644 --- a/test/dummy/app/mailers/application_mailer.rb +++ b/test/dummy/app/mailers/application_mailer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ApplicationMailer < ActionMailer::Base default from: 'from@example.com' layout 'mailer' diff --git a/test/dummy/app/models/application_record.rb b/test/dummy/app/models/application_record.rb index b63caeb..08dc537 100644 --- a/test/dummy/app/models/application_record.rb +++ b/test/dummy/app/models/application_record.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ApplicationRecord < ActiveRecord::Base primary_abstract_class end diff --git a/test/dummy/app/models/author.rb b/test/dummy/app/models/author.rb new file mode 100644 index 0000000..55c5814 --- /dev/null +++ b/test/dummy/app/models/author.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Author < ApplicationRecord + validates :name, presence: true, length: { maximum: 5 } +end diff --git a/test/dummy/app/models/user.rb b/test/dummy/app/models/user.rb new file mode 100644 index 0000000..2f0d250 --- /dev/null +++ b/test/dummy/app/models/user.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class User < ApplicationRecord + extend ActivejobWeb::JobsHelper + # == Associations ================================================================================================== + has_and_belongs_to_many :executors, class_name: 'ActivejobWeb::Job', join_table: 'activejob_web_job_executors', + association_foreign_key: 'executor_id' + has_many :job_approval_requests + + # == Validations =================================================================================================== + validates :name, presence: true, length: { maximum: 50 } + + # == Scopes ======================================================================================================== + scope :super_admin_users, -> { User.all.limit(1) } + + def self.allow_admin_access? + lambda do |_request| + super_admin_users.include?(activejob_web_current_user) + end + end +end diff --git a/test/dummy/app/views/layouts/application.html.erb b/test/dummy/app/views/layouts/application.html.erb index f72b4ef..51e8dd7 100644 --- a/test/dummy/app/views/layouts/application.html.erb +++ b/test/dummy/app/views/layouts/application.html.erb @@ -1,7 +1,10 @@ - + - Dummy + Activejob Web Gem +
+ Signed in as : <%= activejob_web_current_user.email %> +
<%= csrf_meta_tags %> <%= csp_meta_tag %> diff --git a/test/dummy/bin/rails b/test/dummy/bin/rails index 0739660..a31728a 100755 --- a/test/dummy/bin/rails +++ b/test/dummy/bin/rails @@ -1,4 +1,6 @@ #!/usr/bin/env ruby +# frozen_string_literal: true + APP_PATH = File.expand_path('../config/application', __dir__) require_relative '../config/boot' require 'rails/commands' diff --git a/test/dummy/bin/rake b/test/dummy/bin/rake index 1724048..c199955 100755 --- a/test/dummy/bin/rake +++ b/test/dummy/bin/rake @@ -1,4 +1,6 @@ #!/usr/bin/env ruby +# frozen_string_literal: true + require_relative '../config/boot' require 'rake' Rake.application.run diff --git a/test/dummy/bin/setup b/test/dummy/bin/setup index d6e019a..516b651 100755 --- a/test/dummy/bin/setup +++ b/test/dummy/bin/setup @@ -1,4 +1,6 @@ #!/usr/bin/env ruby +# frozen_string_literal: true + require 'fileutils' # path to your application root. diff --git a/test/dummy/config.ru b/test/dummy/config.ru index ad1fbf2..6dc8321 100644 --- a/test/dummy/config.ru +++ b/test/dummy/config.ru @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file is used by Rack-based servers to start the application. require_relative 'config/environment' diff --git a/test/dummy/config/application.rb b/test/dummy/config/application.rb index 0bdeaf2..d491b46 100644 --- a/test/dummy/config/application.rb +++ b/test/dummy/config/application.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'boot' require 'rails/all' diff --git a/test/dummy/config/boot.rb b/test/dummy/config/boot.rb index c9aef85..6d2cba0 100644 --- a/test/dummy/config/boot.rb +++ b/test/dummy/config/boot.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__) diff --git a/test/dummy/config/environment.rb b/test/dummy/config/environment.rb index 426333b..d5abe55 100644 --- a/test/dummy/config/environment.rb +++ b/test/dummy/config/environment.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Load the Rails application. require_relative 'application' diff --git a/test/dummy/config/environments/development.rb b/test/dummy/config/environments/development.rb index e522e6e..3cd69b8 100644 --- a/test/dummy/config/environments/development.rb +++ b/test/dummy/config/environments/development.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/core_ext/integer/time' Rails.application.configure do @@ -59,6 +61,8 @@ # Suppress logger output for asset requests. config.assets.quiet = true + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true diff --git a/test/dummy/config/environments/production.rb b/test/dummy/config/environments/production.rb index a642818..5e458f7 100644 --- a/test/dummy/config/environments/production.rb +++ b/test/dummy/config/environments/production.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/core_ext/integer/time' Rails.application.configure do @@ -83,7 +85,7 @@ # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name") if ENV['RAILS_LOG_TO_STDOUT'].present? - logger = ActiveSupport::Logger.new(STDOUT) + logger = ActiveSupport::Logger.new($stdout) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end diff --git a/test/dummy/config/environments/test.rb b/test/dummy/config/environments/test.rb index 5f6cef4..8f3f63c 100644 --- a/test/dummy/config/environments/test.rb +++ b/test/dummy/config/environments/test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/core_ext/integer/time' # The test environment is used exclusively to run your application's diff --git a/test/dummy/config/initializers/assets.rb b/test/dummy/config/initializers/assets.rb index fe48fc3..bcafccd 100644 --- a/test/dummy/config/initializers/assets.rb +++ b/test/dummy/config/initializers/assets.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Version of your assets, change this if you want to expire all your assets. diff --git a/test/dummy/config/initializers/content_security_policy.rb b/test/dummy/config/initializers/content_security_policy.rb index 54f47cf..691cfa1 100644 --- a/test/dummy/config/initializers/content_security_policy.rb +++ b/test/dummy/config/initializers/content_security_policy.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Define an application-wide content security policy. diff --git a/test/dummy/config/initializers/filter_parameter_logging.rb b/test/dummy/config/initializers/filter_parameter_logging.rb index 166997c..3df77c5 100644 --- a/test/dummy/config/initializers/filter_parameter_logging.rb +++ b/test/dummy/config/initializers/filter_parameter_logging.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Configure parameters to be filtered from the log file. Use this to limit dissemination of diff --git a/test/dummy/config/initializers/inflections.rb b/test/dummy/config/initializers/inflections.rb index 3860f65..6c78420 100644 --- a/test/dummy/config/initializers/inflections.rb +++ b/test/dummy/config/initializers/inflections.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format. Inflections diff --git a/test/dummy/config/initializers/permissions_policy.rb b/test/dummy/config/initializers/permissions_policy.rb index 00f64d7..50bcf4e 100644 --- a/test/dummy/config/initializers/permissions_policy.rb +++ b/test/dummy/config/initializers/permissions_policy.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Define an application-wide HTTP permissions policy. For further # information see https://developers.google.com/web/updates/2018/06/feature-policy # diff --git a/test/dummy/config/puma.rb b/test/dummy/config/puma.rb index df14fbe..1713441 100644 --- a/test/dummy/config/puma.rb +++ b/test/dummy/config/puma.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Puma can serve each request in a thread from an internal thread pool. # The `threads` method setting takes two numbers: a minimum and maximum. # Any libraries that use thread pools should be configured to match diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb index 262ffd5..e8407da 100644 --- a/test/dummy/config/routes.rb +++ b/test/dummy/config/routes.rb @@ -1,6 +1,5 @@ +# frozen_string_literal: true + Rails.application.routes.draw do # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html - - # Defines the root path route ("/") - # root "articles#index" end diff --git a/test/dummy/db/migrate/20231101105617_create_authors.rb b/test/dummy/db/migrate/20231101105617_create_authors.rb new file mode 100644 index 0000000..61059c3 --- /dev/null +++ b/test/dummy/db/migrate/20231101105617_create_authors.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class CreateAuthors < ActiveRecord::Migration[7.1] + def change + create_table :authors do |t| + t.string :name + + t.timestamps + end + end +end diff --git a/test/dummy/db/migrate/20231118052244_create_users.rb b/test/dummy/db/migrate/20231118052244_create_users.rb new file mode 100644 index 0000000..339c159 --- /dev/null +++ b/test/dummy/db/migrate/20231118052244_create_users.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class CreateUsers < ActiveRecord::Migration[7.1] + def change + create_table :users do |t| + t.string :name + t.string :email, null: false, default: '' + t.string :password, null: false, default: '' + + t.timestamps null: false + end + + add_index :users, :email, unique: true + end +end diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb new file mode 100644 index 0000000..8a47e77 --- /dev/null +++ b/test/dummy/db/schema.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[7.1].define(version: 2023_11_22_042337) do + # These are extensions that must be enabled in order to support this database + enable_extension 'plpgsql' + + create_table 'active_storage_attachments', force: :cascade do |t| + t.string 'name', null: false + t.string 'record_type', null: false + t.bigint 'record_id', null: false + t.bigint 'blob_id', null: false + t.datetime 'created_at', null: false + t.index ['blob_id'], name: 'index_active_storage_attachments_on_blob_id' + t.index %w[record_type record_id name blob_id], name: 'index_active_storage_attachments_uniqueness', unique: true + end + + create_table 'active_storage_blobs', force: :cascade do |t| + t.string 'key', null: false + t.string 'filename', null: false + t.string 'content_type' + t.text 'metadata' + t.string 'service_name', null: false + t.bigint 'byte_size', null: false + t.string 'checksum' + t.datetime 'created_at', null: false + t.index ['key'], name: 'index_active_storage_blobs_on_key', unique: true + end + + create_table 'active_storage_variant_records', force: :cascade do |t| + t.bigint 'blob_id', null: false + t.string 'variation_digest', null: false + t.index %w[blob_id variation_digest], name: 'index_active_storage_variant_records_uniqueness', unique: true + end + + create_table 'activejob_web_job_approval_requests', force: :cascade do |t| + t.integer 'job_execution_id' + t.integer 'approver_id' + t.integer 'response' + t.string 'approver_comments' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + end + + create_table 'activejob_web_job_approvers', id: false, force: :cascade do |t| + t.uuid 'job_id' + t.integer 'approver_id' + end + + create_table 'activejob_web_job_executions', force: :cascade do |t| + t.integer 'requestor_id' + t.uuid 'job_id' + t.string 'requestor_comments' + t.json 'arguments' + t.integer 'status' + t.string 'reason_for_failure' + t.boolean 'auto_execute_on_approval' + t.datetime 'run_at', precision: nil + t.datetime 'execution_started_at', precision: nil + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + end + + create_table 'activejob_web_job_executors', id: false, force: :cascade do |t| + t.uuid 'job_id' + t.integer 'executor_id' + end + + create_table 'activejob_web_jobs', id: false, force: :cascade do |t| + t.uuid 'id', default: -> { 'gen_random_uuid()' }, null: false + t.string 'title' + t.string 'description' + t.json 'input_arguments' + t.integer 'max_run_time' + t.integer 'minimum_approvals_required' + t.integer 'priority' + t.integer 'queue' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + end + + create_table 'authors', force: :cascade do |t| + t.string 'name' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + end + + create_table 'users', force: :cascade do |t| + t.string 'name' + t.string 'email', default: '', null: false + t.string 'password', default: '', null: false + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index ['email'], name: 'index_users_on_email', unique: true + end + + add_foreign_key 'active_storage_attachments', 'active_storage_blobs', column: 'blob_id' + add_foreign_key 'active_storage_variant_records', 'active_storage_blobs', column: 'blob_id' +end diff --git a/test/dummy/db/seeds.rb b/test/dummy/db/seeds.rb new file mode 100644 index 0000000..ee11ad0 --- /dev/null +++ b/test/dummy/db/seeds.rb @@ -0,0 +1,49 @@ +10.times do |i| + job = ActivejobWeb::Job.new( + title: "Job Title #{i + 1}", + description: "Job Description #{i + 1}", + input_arguments: [ + { + "name": "File", + "type": "File", + "allowed_characters": "", + "max_length": "10", + "required": true + }, + { + "name": "Imported Date", + "type": "Date", + "required": true + }, + { + "name": "Imported Date and Time", + "type": "DateTime", + "required": true + }, + { + "name": "client name", + "type": "String", + "required": true, + "allowed_characters": "", + "max_length": "10" + }, + { + "name": "Booking ID", + "type": "integer" + } + ], + max_run_time: 60, + minimum_approvals_required: 2, + priority: 1 + ) + + if job.save + puts "Job #{i + 1} has been created successfully." + else + puts "Error creating Job #{i + 1}: #{job.errors.full_messages.join(', ')}" + end +end +#==== specified the file path and used File.open method to get the file, then attached the file +file_path = "app/assets/images/activejob/web/sample.png" +file = File.open(file_path, 'rb') +ActivejobWeb::Job.first.template_file.attach(io: file, filename: 'sample.png') diff --git a/test/dummy/spec/models/user_spec.rb b/test/dummy/spec/models/user_spec.rb new file mode 100644 index 0000000..7da47d1 --- /dev/null +++ b/test/dummy/spec/models/user_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe User, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/test/fixtures/activejob_web/jobs.yml b/test/fixtures/activejob_web/jobs.yml new file mode 100644 index 0000000..d7a3329 --- /dev/null +++ b/test/fixtures/activejob_web/jobs.yml @@ -0,0 +1,11 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +one: {} +# column: value +# +two: {} +# column: value diff --git a/test/integration/navigation_test.rb b/test/integration/navigation_test.rb index 29e59d8..91d920f 100644 --- a/test/integration/navigation_test.rb +++ b/test/integration/navigation_test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'test_helper' class NavigationTest < ActionDispatch::IntegrationTest diff --git a/test/models/activejob_web/job_test.rb b/test/models/activejob_web/job_test.rb new file mode 100644 index 0000000..e51a999 --- /dev/null +++ b/test/models/activejob_web/job_test.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'test_helper' + +module ActivejobWeb + class JobTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end + end +end diff --git a/test/models/activejob_web_job_test.rb b/test/models/activejob_web_job_test.rb new file mode 100644 index 0000000..3d14188 --- /dev/null +++ b/test/models/activejob_web_job_test.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'test_helper' + +class ActivejobWebJobTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/user_test.rb b/test/models/user_test.rb new file mode 100644 index 0000000..5cc44ed --- /dev/null +++ b/test/models/user_test.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'test_helper' + +class UserTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 3dfa6f5..de45395 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Configure Rails Environment ENV['RAILS_ENV'] = 'test'