Merge pull request #8402 from artoscinote/ma_SCI_11771

Add metadata column to projects and expose it in API [SCI-11771]
This commit is contained in:
Martin Artnik 2025-04-14 12:45:29 +02:00 committed by GitHub
commit 8e949bd68f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 101 additions and 5 deletions

View file

@ -15,11 +15,11 @@ module Api
projects = archived_filter(projects).page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: projects, each_serializer: ProjectSerializer, include: include_params
render jsonapi: projects, each_serializer: ProjectSerializer, scope: { metadata: params['with-metadata'] == 'true' }, include: include_params
end
def show
render jsonapi: @project, serializer: ProjectSerializer, include: include_params
render jsonapi: @project, serializer: ProjectSerializer, scope: { metadata: params['with-metadata'] == 'true' }, include: include_params
end
def create
@ -33,7 +33,7 @@ module Api
project.save!
render jsonapi: project, serializer: ProjectSerializer, status: :created
render jsonapi: project, serializer: ProjectSerializer, scope: { metadata: params['with-metadata'] == 'true' }, status: :created
end
def update
@ -50,7 +50,7 @@ module Api
end
@project.last_modified_by = current_user
@project.save!
render jsonapi: @project, serializer: ProjectSerializer, status: :ok
render jsonapi: @project, serializer: ProjectSerializer, scope: { metadata: params['with-metadata'] == 'true' }, status: :ok
end
def activities
@ -66,7 +66,7 @@ module Api
def project_params
raise TypeError unless params.require(:data).require(:type) == 'projects'
params.require(:data).require(:attributes).permit(:name, :visibility, :archived, :project_folder_id)
params.require(:data).require(:attributes).permit(:name, :visibility, :archived, :project_folder_id, metadata: {})
end
def permitted_includes

View file

@ -0,0 +1,22 @@
# frozen_string_literal: true
module MetadataModel
extend ActiveSupport::Concern
included do
scope :with_metadata_value, lambda { |key, value|
# sanitize key, replace . with -> and replace last -> with ->> to ensure string comparison at last level
db_key =
"metadata->#{key.to_s.split('.').map { |k| "'#{k.parameterize(separator: '_')}'" }.join('->')}".sub(/->(?!.*->)/, '->>')
where("#{db_key} = ?", value.to_s)
}
before_save :sanitize_metadata_keys!
def sanitize_metadata_keys!
return unless metadata
self.metadata = metadata.deep_transform_keys { |k| k.parameterize(separator: '_') }
end
end
end

View file

@ -11,6 +11,7 @@ class Project < ApplicationRecord
include ViewableModel
include PermissionCheckableModel
include Assignable
include MetadataModel
enum visibility: { hidden: 0, visible: 1 }

View file

@ -5,6 +5,7 @@ module Api
class ProjectSerializer < ActiveModel::Serializer
type :projects
attributes :name, :visibility, :start_date, :archived
attribute :metadata, if: -> { scope && scope[:metadata] == true }
belongs_to :project_folder, serializer: ProjectFolderSerializer
has_many :project_comments, key: :comments, serializer: CommentSerializer

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddMetadataToProjects < ActiveRecord::Migration[7.0]
def change
add_column :projects, :metadata, :jsonb
end
end

View file

@ -0,0 +1,56 @@
# frozen_string_literal: true
require 'rails_helper'
describe MetadataModel, type: :concern do
let!(:user) { create :user }
let!(:team) { create :team, created_by: user }
let!(:project_1) do
Project.create!(
name: 'Project 1',
team: user.teams.first,
created_by: user,
metadata: {
status: 'processed',
info: {
tag: 'important',
number: 2
}
}
)
end
let!(:project_2) do
Project.create!(
name: 'Project 2',
team: user.teams.first,
created_by: user,
metadata: {
status: 'failed'
}
)
end
it '#with_metadata_value finds the correct project by metadata value' do
results = Project.with_metadata_value(:status, 'processed')
expect(results.count).to eq 1
expect(results.last.id).to eq project_1.id
end
it '#with_metadata_value finds the correct project by nested metadata value' do
results = Project.with_metadata_value('info.tag', 'important')
expect(results.count).to eq 1
expect(results.last.id).to eq project_1.id
results = Project.with_metadata_value('info.number', 2)
expect(results.count).to eq 1
expect(results.last.id).to eq project_1.id
end
it '#with_metadata_value escapes key input' do
results = nil
expect { results = Project.with_metadata_value("project'->>'tag' = \'one\') AND name", nil) }.to_not raise_error
expect(results.count).to eq 0
end
end

View file

@ -106,6 +106,15 @@ RSpec.describe 'Api::V1::ProjectsController', type: :request do
)
end
it 'When metadata parameter is set to true, it serializes metadata' do
@team1.projects.first.update(metadata: { status: 'processed' })
hash_body = nil
get api_v1_team_project_path(team_id: @team1.id, id: @team1.projects.first.id, 'with-metadata' => 'true'),
headers: @valid_headers
expect { hash_body = json }.not_to raise_exception
expect(hash_body[:data]['attributes']['metadata']['status']).to eq 'processed'
end
it 'When invalid request, user in not member of the team' do
hash_body = nil
get api_v1_team_project_path(team_id: @team2.id,