mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Introduce header and Rails caching for schemas
This commit introduces cached project-scoped work packages schema. Their caches are expired on a key based on - the Project ID - the Type ID - the allowed custom fields for the given project This commit only caches schema, not form endpoints.
This commit is contained in:
@@ -200,6 +200,8 @@ end
|
||||
|
||||
# API gems
|
||||
gem 'grape', '~> 0.10.1'
|
||||
gem 'grape-cache_control', '~> 1.0.1'
|
||||
|
||||
gem 'roar', '~> 1.0.0'
|
||||
gem 'reform', '~> 1.2.6', require: false
|
||||
|
||||
|
||||
@@ -286,6 +286,8 @@ GEM
|
||||
rack-accept
|
||||
rack-mount
|
||||
virtus (>= 1.0.0)
|
||||
grape-cache_control (1.0.1)
|
||||
grape (~> 0.3)
|
||||
gravatar_image_tag (1.2.0)
|
||||
hashie (3.4.1)
|
||||
health_check (1.5.1)
|
||||
@@ -595,6 +597,7 @@ DEPENDENCIES
|
||||
globalize (~> 5.0.1)
|
||||
gon (~> 4.0)
|
||||
grape (~> 0.10.1)
|
||||
grape-cache_control (~> 1.0.1)
|
||||
gravatar_image_tag (~> 1.2.0)
|
||||
health_check
|
||||
htmldiff
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
module API
|
||||
module Caching
|
||||
module Helpers
|
||||
def with_etag!(key)
|
||||
etag = %(W/"#{::Digest::SHA1.hexdigest(key.to_s)}")
|
||||
error!('Not Modified'.freeze, 304) if headers['If-None-Match'.freeze] == etag
|
||||
|
||||
header 'ETag'.freeze, etag
|
||||
end
|
||||
|
||||
##
|
||||
# Store a represented object in its JSON representation
|
||||
def cache(key, args = {})
|
||||
# Save serialization since we're only dealing with strings here
|
||||
args[:raw] = true
|
||||
|
||||
json = Rails.cache.fetch(key, args) {
|
||||
result = yield
|
||||
result.to_json
|
||||
}
|
||||
|
||||
::API::Caching::StoredRepresenter.new json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
module API
|
||||
module Caching
|
||||
class StoredRepresenter
|
||||
def initialize(json)
|
||||
@json = json
|
||||
end
|
||||
|
||||
def to_json
|
||||
@json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -66,6 +66,7 @@ module API
|
||||
|
||||
use OpenProject::Authentication::Manager
|
||||
|
||||
helpers API::Caching::Helpers
|
||||
helpers do
|
||||
def current_user
|
||||
User.current
|
||||
|
||||
@@ -126,9 +126,6 @@ module API
|
||||
|
||||
schema :spent_time,
|
||||
type: 'Duration',
|
||||
show_if: -> (_) do
|
||||
current_user_allowed_to(:view_time_entries, context: represented.project)
|
||||
end,
|
||||
required: false
|
||||
|
||||
schema :percentage_done,
|
||||
|
||||
@@ -46,6 +46,10 @@ module API
|
||||
def raise404
|
||||
raise ::API::Errors::NotFound.new
|
||||
end
|
||||
|
||||
def cache_key(project_id, type_id)
|
||||
"api/v3/work_packages/schema/#{project_id}-#{type_id}"
|
||||
end
|
||||
end
|
||||
|
||||
# The schema identifier is an artificial identifier that is composed of a work packages
|
||||
@@ -56,25 +60,30 @@ module API
|
||||
namespace ':project-:type' do
|
||||
before do
|
||||
begin
|
||||
project = Project.find(params[:project])
|
||||
type = Type.find(params[:type])
|
||||
@project = Project.find(params[:project])
|
||||
@type = Type.find(params[:type])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
raise404
|
||||
end
|
||||
|
||||
authorize(:view_work_packages, context: project) do
|
||||
authorize(:view_work_packages, context: @project) do
|
||||
raise404
|
||||
end
|
||||
|
||||
schema = TypedWorkPackageSchema.new(project: project, type: type)
|
||||
self_link = api_v3_paths.work_package_schema(project.id, type.id)
|
||||
@representer = WorkPackageSchemaRepresenter.create(schema,
|
||||
self_link: self_link,
|
||||
current_user: current_user)
|
||||
# Compare with ETag composed of project and customizations
|
||||
# to avoid evaluating the server request
|
||||
@custom_fields = @project.all_work_package_custom_fields
|
||||
with_etag! "#{@project.id}/#{@custom_fields.count}/#{@custom_fields.to_param}"
|
||||
end
|
||||
|
||||
get do
|
||||
@representer
|
||||
cache(key: [cache_key(@project.id, @type.id), @custom_fields]) do
|
||||
schema = TypedWorkPackageSchema.new(project: @project, type: @type)
|
||||
self_link = api_v3_paths.work_package_schema(@project.id, @type.id)
|
||||
WorkPackageSchemaRepresenter.create(schema,
|
||||
self_link: self_link,
|
||||
current_user: nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -280,18 +280,6 @@ describe ::API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter do
|
||||
let(:required) { false }
|
||||
let(:writable) { false }
|
||||
end
|
||||
|
||||
context 'not allowed to view time entries' do
|
||||
before do
|
||||
allow(current_user).to receive(:allowed_to?).with(:view_time_entries,
|
||||
work_package.project)
|
||||
.and_return false
|
||||
end
|
||||
|
||||
it 'does not show spentTime' do
|
||||
is_expected.not_to have_json_path('spentTime')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'percentageDone' do
|
||||
|
||||
@@ -50,6 +50,10 @@ describe API::V3::WorkPackages::Schema::WorkPackageSchemasAPI, type: :request do
|
||||
it 'should return HTTP 200' do
|
||||
expect(last_response.status).to eql(200)
|
||||
end
|
||||
|
||||
it 'should set a weak ETag' do
|
||||
expect(last_response.headers['ETag']).to match(/W\/\"\w+\"/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'id is too long' do
|
||||
|
||||
Reference in New Issue
Block a user