Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cv2 4095 shared feed cluster data model api backend #1799

Closed
1 change: 1 addition & 0 deletions app/graph/types/cluster_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def items(feed_id:)
argument :feed_id, GraphQL::Types::Int, required: true, camelize: false
end

# TODO: review join query based on new data model
def claim_descriptions(feed_id:)
Cluster.find_if_can(object.id, context[:ability])
feed = Feed.find_if_can(feed_id.to_i, context[:ability])
Expand Down
2 changes: 1 addition & 1 deletion app/lib/check_elastic_search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def store_elasticsearch_data(keys, data)

def get_es_doc_obj
obj = self.is_annotation? ? self.annotated : self
obj = obj.class.name == 'Cluster' ? obj.project_media : obj
obj = obj.class.name == 'Cluster' ? obj.center : obj
obj&.id
end

Expand Down
11 changes: 7 additions & 4 deletions app/models/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,12 @@ def admin_perms
can :duplicate, Team, :id => @context_team.id
can :set_privacy, Project, :team_id => @context_team.id
can :read_feed_invitations, Feed, :team_id => @context_team.id
can [:create, :update, :read, :destroy], [Feed, FeedTeam], :team_id => @context_team.id
can :destroy, Feed, :team_id => @context_team.id
can :destroy, Cluster, { feed: { team_id: @context_team.id } }
can [:create, :update], FeedTeam, :team_id => @context_team.id
can [:create, :update], FeedInvitation, { feed: { team_id: @context_team.id } }
can :destroy, FeedTeam do |obj|
obj.team.id == @context_team.id || obj.feed.team.id == @context_team.id
obj.team_id == @context_team.id || obj.feed.team_id == @context_team.id
end
end

Expand Down Expand Up @@ -107,9 +109,10 @@ def editor_perms
can :send, TiplineMessage do |obj|
obj.team_id == @context_team.id
end
can [:read], [Feed, FeedTeam], :team_id => @context_team.id
can [:read], FeedTeam, :team_id => @context_team.id
can [:read], FeedInvitation, { feed: { team_id: @context_team.id } }
can [:create, :update], Feed, :team_id => @context_team.id
can [:read, :create, :update], Feed, :team_id => @context_team.id
can [:read, :create, :update], Cluster, { feed: { team_id: @context_team.id } }
end

def collaborator_perms
Expand Down
76 changes: 23 additions & 53 deletions app/models/cluster.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
class Cluster < ApplicationRecord
include CheckElasticSearch

belongs_to :project_media # Item that is the cluster center
has_many :project_medias, dependent: :nullify, after_add: [:update_cached_fields, :update_elasticsearch_and_timestamps] # Items that belong to the cluster
validates_presence_of :project_media_id
validates_uniqueness_of :project_media_id
validate :center_is_not_part_of_another_cluster
has_many :cluster_project_medias, dependent: :destroy
has_many :project_medias, through: :cluster_project_medias

belongs_to :feed

after_destroy :update_elasticsearch

def center
self.project_media
self.items.first
end

def items
Expand All @@ -26,20 +26,19 @@ def get_requests_count

def get_team_names
data = {}
self.project_medias.group(:team_id).count.keys.collect{ |tid| Team.find_by_id(tid)&.name }
tids = self.project_medias.group(:team_id).count.keys
Team.where(id: tids).find_each { |t| data[t.id] = t.name }
data
end

def get_names_of_teams_that_fact_checked_it
data = {}
j = "INNER JOIN project_medias pm ON annotations.annotated_type = 'ProjectMedia' AND annotations.annotated_id = pm.id INNER JOIN clusters c ON c.id = pm.cluster_id"
j = "INNER JOIN project_medias pm ON annotations.annotated_type = 'ProjectMedia' AND annotations.annotated_id = pm.id INNER JOIN cluster_project_medias cpm ON cpm.project_media_id = pm.id"
tids = Dynamic.where(annotation_type: 'report_design').where('data LIKE ?', '%state: published%')
.joins(j).where('c.id' => self.id).group(:team_id).count.keys
.joins(j).where('cpm.cluster_id' => self.id).group(:team_id).count.keys
Team.where(id: tids).find_each { |t| data[t.id] = t.name }
# update ES count field
pm = self.project_media
pm = self.center
unless pm.nil?
options = {
keys: ['cluster_published_reports_count'],
Expand All @@ -53,7 +52,7 @@ def get_names_of_teams_that_fact_checked_it
end

def claim_descriptions
ClaimDescription.joins(:project_media).where('project_medias.cluster_id' => self.id)
ClaimDescription.where(project_media_id: self.project_media_ids)
end

cached_field :team_names,
Expand All @@ -71,7 +70,7 @@ def claim_descriptions
{
model: Dynamic,
if: proc { |d| d.annotation_type == 'report_design' },
affected_ids: proc { |d| ProjectMedia.where(id: d.annotated.related_items_ids).group(:cluster_id).count.keys.reject{ |cid| cid.nil? } },
affected_ids: proc { |d| ClusterProjectMedia.where(project_media_id: d.annotated.related_items_ids).map(&:cluster_id).uniq },
events: {
save: :recalculate
}
Expand All @@ -87,10 +86,10 @@ def claim_descriptions
{
model: TiplineRequest,
if: proc { |tr| tr.associated_type == 'ProjectMedia' },
affected_ids: proc { |tr| ProjectMedia.where(id: tr.associated.related_items_ids).group(:cluster_id).count.keys.reject{ |cid| cid.nil? } },
affected_ids: proc { |tr| ClusterProjectMedia.where(project_media_id: tr.associated.related_items_ids).map(&:cluster_id).uniq },
events: {
create: :cached_field_cluster_requests_count_create,
destroy: :cached_field_cluster_requests_count_destroy
create: :recalculate,
destroy: :recalculate
}
}
]
Expand All @@ -113,44 +112,15 @@ def cached_field_fact_checked_by_team_names_es(value)

private

def center_is_not_part_of_another_cluster
errors.add(:base, I18n.t(:center_is_not_part_of_another_cluster)) if self.center&.cluster_id && self.center&.cluster_id != self.id
end

def update_cached_fields(_item)
self.team_names(true)
self.fact_checked_by_team_names(true)
self.requests_count(true)
end

def update_elasticsearch_and_timestamps(item)
self.first_item_at = item.created_at if item.created_at.to_i < self.first_item_at.to_i || self.first_item_at.to_i == 0
self.last_item_at = item.created_at if item.created_at.to_i > self.last_item_at.to_i
self.skip_check_ability = true
self.save!
# update ES
pm = self.project_media
data = {
'cluster_size' => self.project_medias.count,
'cluster_first_item_at' => self.first_item_at.to_i,
'cluster_last_item_at' => self.last_item_at.to_i,
'cluster_published_reports' => self.fact_checked_by_team_names.keys,
'cluster_published_reports_count' => self.fact_checked_by_team_names.size,
'cluster_requests_count' => self.requests_count,
'cluster_teams' => self.team_names.keys,
}
options = { keys: data.keys, data: data, pm_id: pm.id }
model = { klass: pm.class.name, id: pm.id }
ElasticSearchWorker.perform_in(1.second, YAML::dump(model), YAML::dump(options), 'update_doc')
end

def update_elasticsearch
keys = ['cluster_size', 'cluster_first_item_at', 'cluster_last_item_at', 'cluster_published_reports_count', 'cluster_requests_count']
pm = self.project_media
data = {}
keys.each { |k| data[k] = 0 }
options = { keys: keys, data: data, pm_id: pm.id }
model = { klass: pm.class.name, id: pm.id }
ElasticSearchWorker.perform_in(1.second, YAML::dump(model), YAML::dump(options), 'update_doc')
pm = self.center
unless pm.nil?
keys = ['cluster_size', 'cluster_first_item_at', 'cluster_last_item_at', 'cluster_published_reports_count', 'cluster_requests_count']
data = {}
keys.each { |k| data[k] = 0 }
options = { keys: keys, data: data, pm_id: pm.id }
model = { klass: pm.class.name, id: pm.id }
ElasticSearchWorker.perform_in(1.second, YAML::dump(model), YAML::dump(options), 'update_doc')
end
end
end
48 changes: 48 additions & 0 deletions app/models/cluster_project_media.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
class ClusterProjectMedia < ApplicationRecord
include CheckElasticSearch

belongs_to :cluster, optional: true
belongs_to :project_media, optional: true

validates_presence_of :cluster_id
validates_presence_of :project_media_id

after_create :update_cached_fields, :update_elasticsearch_and_timestamps, :update_project_medias_count
after_destroy :update_project_medias_count

private

def update_cached_fields
cluster = self.cluster
cluster.team_names(true)
cluster.fact_checked_by_team_names(true)
cluster.requests_count(true)
end

def update_elasticsearch_and_timestamps
item = self.project_media
cluster = self.cluster
cluster.first_item_at = item.created_at if item.created_at.to_i < cluster.first_item_at.to_i || cluster.first_item_at.to_i == 0
cluster.last_item_at = item.created_at if item.created_at.to_i > cluster.last_item_at.to_i
cluster.skip_check_ability = true
cluster.save!
# update ES
pm = cluster.center
data = {
'cluster_size' => cluster.project_medias.count,
'cluster_first_item_at' => cluster.first_item_at.to_i,
'cluster_last_item_at' => cluster.last_item_at.to_i,
'cluster_published_reports' => cluster.fact_checked_by_team_names.keys,
'cluster_published_reports_count' => cluster.fact_checked_by_team_names.size,
'cluster_requests_count' => cluster.requests_count,
'cluster_teams' => cluster.team_names.keys,
}
options = { keys: data.keys, data: data, pm_id: pm.id }
model = { klass: pm.class.name, id: pm.id }
ElasticSearchWorker.perform_in(1.second, YAML::dump(model), YAML::dump(options), 'update_doc')
end

def update_project_medias_count
self.cluster.update_columns(project_medias_count: self.cluster.project_medias.count)
end
end
3 changes: 2 additions & 1 deletion app/models/concerns/project_media_associations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ module ProjectMediaAssociations
has_many :targets, through: :source_relationships, source: :target
has_many :project_media_users, dependent: :destroy
has_many :project_media_requests, dependent: :destroy
has_many :cluster_project_medias, dependent: :destroy
has_many :clusters, through: :cluster_project_medias
has_one :claim_description, dependent: :destroy
belongs_to :cluster, counter_cache: :project_medias_count, optional: true
belongs_to :source, optional: true
has_many :tipline_requests, as: :associated
has_annotations
Expand Down
1 change: 1 addition & 0 deletions app/models/feed.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class Feed < ApplicationRecord
has_many :feed_teams, dependent: :restrict_with_error
has_many :teams, through: :feed_teams
has_many :feed_invitations, dependent: :destroy
has_many :clusters
belongs_to :user, optional: true
belongs_to :saved_search, optional: true
belongs_to :team, optional: true
Expand Down
10 changes: 10 additions & 0 deletions app/models/project_media.rb
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,16 @@ def apply_rules_and_actions_on_update
self.team.apply_rules_and_actions(self, rule_ids)
end

def cluster_by_feed(feed_id)
Cluster.where(feed_id: feed_id).joins('INNER JOIN cluster_project_medias cpm ON cpm.cluster_id = clusters.id')
.where('cpm.project_media_id = ?', self.id)
end

# Define this method to keep check-web working
def cluster
self.clusters.first
end

protected

def add_extra_elasticsearch_data(ms)
Expand Down
8 changes: 0 additions & 8 deletions app/models/tipline_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,6 @@ def smooch_user_request_language
self.language.to_s
end

def cached_field_cluster_requests_count_create(target)
target.requests_count + 1
end

def cached_field_cluster_requests_count_destroy(target)
target.requests_count - 1
end

def associated_graphql_id
Base64.encode64("#{self.associated_type}/#{self.associated_id}")
end
Expand Down
12 changes: 12 additions & 0 deletions db/migrate/20240212055200_create_cluster_project_medias.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class CreateClusterProjectMedias < ActiveRecord::Migration[6.1]
def change
create_table :cluster_project_medias do |t|
t.references :cluster
t.references :project_media
end
add_index :cluster_project_medias, [:cluster_id, :project_media_id], unique: true
add_reference :clusters, :feed, index: true
remove_reference :clusters, :project_media, index: true
remove_reference :project_medias, :cluster, index: true
end
end
17 changes: 11 additions & 6 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2024_01_15_101312) do
ActiveRecord::Schema.define(version: 2024_02_12_055200) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -220,14 +220,22 @@
t.index ["user_id"], name: "index_claim_descriptions_on_user_id"
end

create_table "cluster_project_medias", force: :cascade do |t|
t.bigint "cluster_id"
t.bigint "project_media_id"
t.index ["cluster_id", "project_media_id"], name: "index_cluster_project_medias_on_cluster_id_and_project_media_id", unique: true
t.index ["cluster_id"], name: "index_cluster_project_medias_on_cluster_id"
t.index ["project_media_id"], name: "index_cluster_project_medias_on_project_media_id"
end

create_table "clusters", force: :cascade do |t|
t.integer "project_medias_count", default: 0
t.integer "project_media_id"
t.datetime "first_item_at"
t.datetime "last_item_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["project_media_id"], name: "index_clusters_on_project_media_id", unique: true
t.bigint "feed_id"
t.index ["feed_id"], name: "index_clusters_on_feed_id"
end

create_table "dynamic_annotation_annotation_types", primary_key: "annotation_type", id: :string, force: :cascade do |t|
Expand Down Expand Up @@ -276,7 +284,6 @@
t.index ["field_type"], name: "index_dynamic_annotation_fields_on_field_type"
t.index ["value"], name: "fetch_unique_id", unique: true, where: "(((field_name)::text = 'external_id'::text) AND (value <> ''::text) AND (value <> '\"\"'::text))"
t.index ["value"], name: "index_status", where: "((field_name)::text = 'verification_status_status'::text)"
t.index ["value"], name: "smooch_request_message_id_unique_id", unique: true, where: "(((field_name)::text = 'smooch_message_id'::text) AND (value <> ''::text) AND (value <> '\"\"'::text))"
t.index ["value"], name: "smooch_user_unique_id", unique: true, where: "(((field_name)::text = 'smooch_user_id'::text) AND (value <> ''::text) AND (value <> '\"\"'::text))"
t.index ["value"], name: "translation_request_id", unique: true, where: "((field_name)::text = 'translation_request_id'::text)"
t.index ["value_json"], name: "index_dynamic_annotation_fields_on_value_json", using: :gin
Expand Down Expand Up @@ -452,7 +459,6 @@
t.integer "media_id"
t.integer "user_id"
t.integer "source_id"
t.integer "cluster_id"
t.integer "team_id"
t.jsonb "channel", default: {"main"=>0}
t.boolean "read", default: false, null: false
Expand All @@ -466,7 +472,6 @@
t.string "custom_title"
t.string "title_field"
t.index ["channel"], name: "index_project_medias_on_channel"
t.index ["cluster_id"], name: "index_project_medias_on_cluster_id"
t.index ["last_seen"], name: "index_project_medias_on_last_seen"
t.index ["media_id"], name: "index_project_medias_on_media_id"
t.index ["project_id"], name: "index_project_medias_on_project_id"
Expand Down
10 changes: 4 additions & 6 deletions lib/check_basic_abilities.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,6 @@ def extra_perms_for_all_users
!obj.team.private || @user.cached_teams.include?(obj.team.id)
end

can :read, Cluster do |obj|
shared_team_ids = @context_team.shared_teams.map(&:id)
team_ids = (shared_team_ids & @user.cached_teams)
ProjectMedia.where(cluster_id: obj.id, team_id: shared_team_ids).exists? && !team_ids.empty?
end

can :read, BotUser do |obj|
obj.get_approved || @user.cached_teams.include?(obj.team_author_id)
end
Expand All @@ -113,6 +107,10 @@ def extra_perms_for_all_users
!(@user.cached_teams & obj.team_ids).empty?
end

can :read, Cluster do |obj|
!(@user.cached_teams & obj.feed.team_ids).empty?
end

can :read, FeedTeam do |obj|
@user.cached_teams.include?(obj.team_id)
end
Expand Down
Loading
Loading