Rewrite status as module

pull/8453/head
Oliver Günther 4 years ago
parent b8bc50bb8d
commit f7d4439f65
No known key found for this signature in database
GPG Key ID: A3A8BDAD7C0C552C
  1. 6
      Gemfile.lock
  2. 1
      Gemfile.modules
  3. 23
      app/models/delayed/job/status.rb
  4. 136
      app/workers/active_job/job_status_listener.rb
  5. 1
      frontend/src/app/modules/job-status/display-job-page/display-job-page.component.html
  6. 12
      frontend/src/app/modules/job-status/display-job-page/display-job-page.component.ts
  7. 68
      frontend/src/app/modules/job-status/openproject-job-status.module.ts
  8. 6
      frontend/src/app/modules/router/openproject.routes.ts
  9. 51
      modules/job_status/app/controllers/job_status/job_statuses_controller.rb
  10. 21
      modules/job_status/app/models/job_status/status.rb
  11. 6
      modules/job_status/config/routes.rb
  12. 0
      modules/job_status/db/migrate/20200422105623_add_job_status.rb
  13. 0
      modules/job_status/db/migrate/20200610124259_extend_job_status.rb
  14. 11
      modules/job_status/lib/open_project/job_status.rb
  15. 50
      modules/job_status/lib/open_project/job_status/engine.rb
  16. 138
      modules/job_status/lib/open_project/job_status/event_listener.rb
  17. 30
      modules/job_status/lib/openproject-job_status.rb
  18. 11
      modules/job_status/openproject-job_status.gemspec
  19. 3
      modules/job_status/spec/factories/delayed_job_status_factory.rb

@ -114,6 +114,11 @@ PATH
specs:
grids (1.0.0)
PATH
remote: modules/job_status
specs:
openproject-job_status (1.0.0)
PATH
remote: modules/ldap_groups
specs:
@ -1056,6 +1061,7 @@ DEPENDENCIES
openproject-documents!
openproject-github_integration!
openproject-global_roles!
openproject-job_status!
openproject-ldap_groups!
openproject-meeting!
openproject-openid_connect!

@ -38,6 +38,7 @@ group :opf_plugins do
gem 'openproject-github_integration', path: 'modules/github_integration'
gem 'openproject-ldap_groups', path: 'modules/ldap_groups'
gem 'openproject-recaptcha', path: 'modules/recaptcha'
gem 'openproject-job_status', path: 'modules/job_status'
gem 'grids', path: 'modules/grids'
gem 'my_page', path: 'modules/my_page'

@ -1,23 +0,0 @@
module Delayed
class Job
class Status < ApplicationRecord
self.table_name = 'delayed_job_statuses'
belongs_to :user
belongs_to :reference, polymorphic: true
enum status: {
in_queue: 'in_queue',
error: 'error',
in_process: 'in_process',
success: 'success',
failure: 'failure',
cancelled: 'cancelled'
}
def self.of_reference(reference)
where(reference: reference)
end
end
end
end

@ -1,136 +0,0 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2020 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
module ActiveJob
class JobStatusListener
class << self
def register!
# Listen to enqueues
ActiveSupport::Notifications.subscribe(/enqueue(_at)?\.active_job/) do |_name, job:, **_args|
Rails.logger.debug { "Enqueuing background job #{job.inspect}" }
for_statused_jobs(job) { create_job_status(job) }
end
# Start of process
ActiveSupport::Notifications.subscribe('perform_start.active_job') do |job:, **_args|
Rails.logger.debug { "Background job #{job.inspect} is being started" }
for_statused_jobs(job) { on_start(job) }
end
# Complete, or failure
ActiveSupport::Notifications.subscribe('perform.active_job') do |job:, exception_object: nil, **_args|
Rails.logger.debug do
successful = exception_object ? "with error: #{exception_object}" : "successful"
"Background job #{job.inspect} was performed #{successful}."
end
for_statused_jobs(job) { on_performed(job, exception_object) }
end
# Retry stopped -> failure
ActiveSupport::Notifications.subscribe('retry_stopped.active_job') do |job:, error: nil, **_args|
Rails.logger.debug { "Background job #{job.inspect} no longer retrying due to: #{error}" }
for_statused_jobs(job) { on_performed(job, error) }
end
# Retry enqueued
ActiveSupport::Notifications.subscribe('enqueue_retry.active_job') do |job, error: nil, **_args|
Rails.logger.debug { "Background job #{job.inspect} is being retried after error: #{error}" }
for_statused_jobs(job) { on_requeue(job, error) }
end
# Discarded job
ActiveSupport::Notifications.subscribe('discard.active_job') do |job:, error: nil, **_args|
Rails.logger.debug { "Background job #{job.inspect} is being discarded after error: #{error}" }
for_statused_jobs(job) { on_cancelled(job, error) }
end
end
private
##
# Yiels the block if the job
# handles statuses
def for_statused_jobs(job)
yield if job.store_status?
end
##
# Create a status object when enqueuing a
# new job through activejob that stores statuses
def create_job_status(job)
Delayed::Job::Status.create status: :in_queue,
reference: job.status_reference,
user: User.current,
job_id: job.job_id
end
##
# On start processing a new job
def on_start(job)
update_status job, code: :in_process
end
##
# On requeuing a job after error
def on_requeue(job, error)
update_status job,
code: :in_queue,
message: I18n.t('background_jobs.status.error_requeue', message: error)
end
##
# On cancellation due to the given error
def on_cancelled(job, error)
update_status job,
code: :cancelled,
message: I18n.t('background_jobs.status.cancelled_due_to', message: error)
end
##
# On job performed, update status
def on_performed(job, exception_object)
if exception_object
update_status job,
code: :failure,
message: exception_object.to_s
else
update_status job, code: :success
end
end
##
# Update the status code for a given job
def update_status(job, code:, message: nil)
Delayed::Job::Status
.where(job_id: job.job_id)
.update_all(status: code, message: message)
end
end
end
end

@ -0,0 +1,12 @@
import {Component} from "@angular/core";
import {StateService} from "@uirouter/core";
@Component({
templateUrl: './display-job-page.component.html'
})
export class DisplayJobPageComponent {
jobId:string = this.$state.params.jobId;
constructor(private $state:StateService) {
}
}

@ -0,0 +1,68 @@
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2020 the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See docs/COPYRIGHT.rdoc for more details.
// ++
import {OpenprojectCommonModule} from 'core-app/modules/common/openproject-common.module';
import {NgModule} from '@angular/core';
import {FullCalendarModule} from '@fullcalendar/angular';
import {WorkPackagesCalendarEntryComponent} from "core-app/modules/calendar/wp-calendar-entry/wp-calendar-entry.component";
import {WorkPackagesCalendarController} from "core-app/modules/calendar/wp-calendar/wp-calendar.component";
import {OpenprojectWorkPackagesModule} from "core-app/modules/work_packages/openproject-work-packages.module";
import {Ng2StateDeclaration, UIRouterModule} from "@uirouter/angular";
import {TimeEntryCalendarComponent} from "core-app/modules/calendar/te-calendar/te-calendar.component";
import {OpenprojectFieldsModule} from "core-app/modules/fields/openproject-fields.module";
import {OpenprojectTimeEntriesModule} from "core-app/modules/time_entries/openproject-time-entries.module";
import {DisplayJobPageComponent} from "core-app/modules/job-status/display-job-page/display-job-page.component";
import {ApplicationBaseComponent} from "core-app/modules/router/base/application-base.component";
export const JOB_STATUS_ROUTE:Ng2StateDeclaration[] = [
{
name: 'job-statuses',
url: '/job_statuses/{jobId:[a-z0-9-]+}',
parent: 'root',
component: DisplayJobPageComponent,
data: {
bodyClasses: 'router--job-statuses'
}
}
];
@NgModule({
imports: [
// Commons
OpenprojectCommonModule,
// Routes for /job_statuses/:uuid
UIRouterModule.forChild({ states: JOB_STATUS_ROUTE }),
],
declarations: [
DisplayJobPageComponent
]
})
export class OpenProjectJobStatusModule {
}

@ -63,6 +63,12 @@ export const OPENPROJECT_ROUTES:Ng2StateDeclaration[] = [
url: '/bcf',
loadChildren: () => import('../bim/ifc_models/openproject-ifc-models.module').then(m => m.OpenprojectIFCModelsModule)
},
{
name: 'job-statuses.**',
parent: 'root',
url: '/job_statuses',
loadChildren: () => import('../job-status/openproject-job-status.module').then(m => m.OpenProjectJobStatusModule)
},
];
/**

@ -0,0 +1,51 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2020 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
module JobStatus
class JobStatusesController < ::ApplicationController
before_action :authorize_on_job
layout 'angular'
def show
# Frontend will handle rendering
# but we will need to render with layout
render html: '', layout: true
end
private
def authorize_on_job
return if JobStatus::Status
.where(job_id: params[:job_uuid], user_id: current_user.id)
.exists?
render_404
end
end
end

@ -0,0 +1,21 @@
module JobStatus
class Status < ApplicationRecord
self.table_name = 'delayed_job_statuses'
belongs_to :user
belongs_to :reference, polymorphic: true
enum status: {
in_queue: 'in_queue',
error: 'error',
in_process: 'in_process',
success: 'success',
failure: 'failure',
cancelled: 'cancelled'
}
def self.of_reference(reference)
where(reference: reference)
end
end
end

@ -0,0 +1,6 @@
OpenProject::Application.routes.draw do
resources :job_statuses,
param: :job_uuid,
controller: 'job_status/job_statuses',
only: %i[show]
end

@ -1,5 +1,4 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2020 the OpenProject GmbH
@ -28,8 +27,8 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
# Extends the ActiveJob adapter in use (DelayedJob) by a Status which lives
# indenpendently from the job itself (which is deleted once successful or after max attempts).
# That way, the result of a background job is available even after the original job is gone.
::ActiveJob::JobStatusListener.register!
module OpenProject
module JobStatus
require 'open_project/job_status/engine'
end
end

@ -0,0 +1,50 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2020 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'open_project/plugins'
module OpenProject::JobStatus
class Engine < ::Rails::Engine
engine_name :openproject_job_status
include OpenProject::Plugins::ActsAsOpEngine
register 'openproject-job_status',
author_url: 'https://www.openproject.com',
bundled: true
config.to_prepare do
# Extends the ActiveJob adapter in use (DelayedJob) by a Status which lives
# indenpendently from the job itself (which is deleted once successful or after max attempts).
# That way, the result of a background job is available even after the original job is gone.
EventListener.register!
end
end
end

@ -0,0 +1,138 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2020 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
module OpenProject
module JobStatus
class EventListener
class << self
def register!
# Listen to enqueues
ActiveSupport::Notifications.subscribe(/enqueue(_at)?\.active_job/) do |_name, job:, **_args|
Rails.logger.debug { "Enqueuing background job #{job.inspect}" }
for_statused_jobs(job) { create_job_status(job) }
end
# Start of process
ActiveSupport::Notifications.subscribe('perform_start.active_job') do |job:, **_args|
Rails.logger.debug { "Background job #{job.inspect} is being started" }
for_statused_jobs(job) { on_start(job) }
end
# Complete, or failure
ActiveSupport::Notifications.subscribe('perform.active_job') do |job:, exception_object: nil, **_args|
Rails.logger.debug do
successful = exception_object ? "with error: #{exception_object}" : "successful"
"Background job #{job.inspect} was performed #{successful}."
end
for_statused_jobs(job) { on_performed(job, exception_object) }
end
# Retry stopped -> failure
ActiveSupport::Notifications.subscribe('retry_stopped.active_job') do |job:, error: nil, **_args|
Rails.logger.debug { "Background job #{job.inspect} no longer retrying due to: #{error}" }
for_statused_jobs(job) { on_performed(job, error) }
end
# Retry enqueued
ActiveSupport::Notifications.subscribe('enqueue_retry.active_job') do |job, error: nil, **_args|
Rails.logger.debug { "Background job #{job.inspect} is being retried after error: #{error}" }
for_statused_jobs(job) { on_requeue(job, error) }
end
# Discarded job
ActiveSupport::Notifications.subscribe('discard.active_job') do |job:, error: nil, **_args|
Rails.logger.debug { "Background job #{job.inspect} is being discarded after error: #{error}" }
for_statused_jobs(job) { on_cancelled(job, error) }
end
end
private
##
# Yiels the block if the job
# handles statuses
def for_statused_jobs(job)
yield if job.store_status?
end
##
# Create a status object when enqueuing a
# new job through activejob that stores statuses
def create_job_status(job)
Delayed::Job::Status.create status: :in_queue,
reference: job.status_reference,
user: User.current,
job_id: job.job_id
end
##
# On start processing a new job
def on_start(job)
update_status job, code: :in_process
end
##
# On requeuing a job after error
def on_requeue(job, error)
update_status job,
code: :in_queue,
message: I18n.t('background_jobs.status.error_requeue', message: error)
end
##
# On cancellation due to the given error
def on_cancelled(job, error)
update_status job,
code: :cancelled,
message: I18n.t('background_jobs.status.cancelled_due_to', message: error)
end
##
# On job performed, update status
def on_performed(job, exception_object)
if exception_object
update_status job,
code: :failure,
message: exception_object.to_s
else
update_status job, code: :success
end
end
##
# Update the status code for a given job
def update_status(job, code:, message: nil)
Delayed::Job::Status
.where(job_id: job.job_id)
.update_all(status: code, message: message)
end
end
end
end
end

@ -0,0 +1,30 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2020 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'open_project/job_status'

@ -0,0 +1,11 @@
# encoding: UTF-8
Gem::Specification.new do |s|
s.name = 'openproject-job_status'
s.version = '1.0.0'
s.authors = 'OpenProject GmbH'
s.email = 'info@openproject.com'
s.summary = 'OpenProject Job status'
s.description = 'Listing and status of background jobs'
s.license = 'GPLv3'
end

@ -27,7 +27,6 @@
#++
FactoryBot.define do
factory :delayed_job_status, class: Delayed::Job::Status do
factory :delayed_job_status, class: ::JobStatus::Status do
end
end
Loading…
Cancel
Save