Skip to content

Commit

Permalink
CV2-4475: Add Explainer data model and graphql api (#1860)
Browse files Browse the repository at this point in the history
* CV2-4475: add explainer data model

* CV2-4497: add graphql api

* CV2-4475: apply PR comments and add more validation

* CV2-4475: add missing tests

* CV2-4475: fix test coverage

* CV2-4475: apply PR comments

* CV2-4475: update graphql schema

* CV2-4475: apply PR comments
  • Loading branch information
melsawy authored Apr 24, 2024
1 parent 5378954 commit 2cfd4f7
Show file tree
Hide file tree
Showing 20 changed files with 2,131 additions and 608 deletions.
25 changes: 25 additions & 0 deletions app/graph/mutations/explainer_mutations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module ExplainerMutations
MUTATION_TARGET = 'explainer'.freeze
PARENTS = ['team'].freeze

module SharedCreateAndUpdateFields
extend ActiveSupport::Concern

included do
argument :title, GraphQL::Types::String, required: true
argument :description, GraphQL::Types::String, required: false
argument :url, GraphQL::Types::String, required: false
argument :language, GraphQL::Types::String, required: false
end
end

class Create < Mutations::CreateMutation
include SharedCreateAndUpdateFields
end

class Update < Mutations::UpdateMutation
include SharedCreateAndUpdateFields
end

class Destroy < Mutations::DestroyMutation; end
end
6 changes: 6 additions & 0 deletions app/graph/types/article_union.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class ArticleUnion < BaseUnion
description 'A union type of all article types we can handle'
possible_types(
ExplainerType,
)
end
21 changes: 21 additions & 0 deletions app/graph/types/explainer_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class ExplainerType < DefaultObject
description "Explainer type"

implements GraphQL::Types::Relay::Node

field :dbid, GraphQL::Types::Int, null: true
field :title, GraphQL::Types::String, null: true
field :description, GraphQL::Types::String, null: true
field :url, GraphQL::Types::String, null: true
field :language, GraphQL::Types::String, null: true
field :user_id, GraphQL::Types::Int, null: true
field :team_id, GraphQL::Types::Int, null: true
field :user, UserType, null: true
field :team, PublicTeamType, null: true

field :tags, TagType.connection_type, null: true

def tags
Tag.where(annotation_type: 'tag', annotated_type: object.class.name, annotated_id: object.id)
end
end
4 changes: 4 additions & 0 deletions app/graph/types/mutation_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,8 @@ class MutationType < BaseObject

field :addNluKeywordToTiplineMenu, mutation: NluMutations::AddKeywordToTiplineMenu
field :removeNluKeywordFromTiplineMenu, mutation: NluMutations::RemoveKeywordFromTiplineMenu

field :createExplainer, mutation: ExplainerMutations::Create
field :updateExplainer, mutation: ExplainerMutations::Update
field :destroyExplainer, mutation: ExplainerMutations::Destroy
end
14 changes: 14 additions & 0 deletions app/graph/types/team_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ def get_shorten_outgoing_urls
object.get_shorten_outgoing_urls
end

field :get_explainers_enabled, GraphQL::Types::Boolean, null: true

def get_explainers_enabled
object.get_explainers_enabled
end

field :public_team, PublicTeamType, null: true

def public_team
Expand Down Expand Up @@ -281,4 +287,12 @@ def shared_teams
def tipline_messages(uid:)
TiplineMessagesPagination.new(object.tipline_messages.where(uid: uid).order('sent_at DESC'))
end

field :articles, ::ArticleUnion.connection_type, null: true do
argument :article_type, GraphQL::Types::String, required: true, camelize: false
end

def articles(article_type:)
object.explainers if article_type == 'explainer'
end
end
1 change: 1 addition & 0 deletions app/models/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ def collaborator_perms
!v_obj.nil? and v_obj.team_id == @context_team.id and v_obj.media.user_id = @user.id
end
can [:create, :update, :read, :destroy], FactCheck, { claim_description: { project_media: { team_id: @context_team.id } } }
can [:create, :update, :read, :destroy], Explainer, team_id: @context_team.id
can [:create, :update, :read], ClaimDescription, { project_media: { team_id: @context_team.id } }
end

Expand Down
14 changes: 13 additions & 1 deletion app/models/claim_description.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class ClaimDescription < ApplicationRecord
include ClaimAndFactCheck
include Article

belongs_to :project_media
has_one :fact_check, dependent: :destroy
Expand All @@ -20,4 +20,16 @@ def fact_checks
def text_fields
['claim_description_content']
end

def article_elasticsearch_data(action = 'create_or_update')
return if self.disable_es_callbacks || RequestStore.store[:disable_es_callbacks]
data = action == 'destroy' ? {
'claim_description_content' => '',
'claim_description_context' => ''
} : {
'claim_description_content' => self.description,
'claim_description_context' => self.context
}
self.index_in_elasticsearch(data)
end
end
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'active_support/concern'

module ClaimAndFactCheck
module Article
extend ActiveSupport::Concern

included do
Expand All @@ -26,11 +26,15 @@ def set_user
end

def update_elasticsearch_data
self.index_in_elasticsearch
self.article_elasticsearch_data
end

def destroy_elasticsearch_data
self.index_in_elasticsearch('destroy')
self.article_elasticsearch_data('destroy')
end

def article_elasticsearch_data(action = 'create_or_update')
# Implement it in the child class
end

def send_to_alegre
Expand All @@ -47,30 +51,7 @@ def notify_bots

protected

def index_in_elasticsearch(action = 'create_or_update')
return if self.disable_es_callbacks || RequestStore.store[:disable_es_callbacks]
data = {}
if self.class.name == 'FactCheck'
data = action == 'destroy' ? {
'fact_check_title' => '',
'fact_check_summary' => '',
'fact_check_url' => '',
'fact_check_languages' => []
} : {
'fact_check_title' => self.title,
'fact_check_summary' => self.summary,
'fact_check_url' => self.url,
'fact_check_languages' => [self.language]
}
else
data = action == 'destroy' ? {
'claim_description_content' => '',
'claim_description_context' => ''
} : {
'claim_description_content' => self.description,
'claim_description_context' => self.context
}
end
def index_in_elasticsearch(data)
# touch project media to update `updated_at` date
pm = self.project_media
pm = ProjectMedia.find_by_id(pm.id)
Expand Down
1 change: 1 addition & 0 deletions app/models/concerns/team_associations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module TeamAssociations
has_many :tipline_messages
has_many :tipline_newsletters
has_many :tipline_requests, as: :associated
has_many :explainers, dependent: :destroy

has_annotations
end
Expand Down
32 changes: 32 additions & 0 deletions app/models/explainer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
class Explainer < ApplicationRecord
include Article

belongs_to :team

has_annotations

before_validation :set_team
validates_format_of :url, with: URI.regexp, allow_blank: true, allow_nil: true
validates_presence_of :team
validate :language_in_allowed_values, unless: proc { |e| e.language.blank? }

def notify_bots
# Nothing to do for Explainer
end

def send_to_alegre
# Nothing to do for Explainer
end

private

def set_team
self.team ||= Team.current
end

def language_in_allowed_values
allowed_languages = self.team.get_languages || ['en']
allowed_languages << 'und'
errors.add(:language, I18n.t(:"errors.messages.invalid_article_language_value")) unless allowed_languages.include?(self.language)
end
end
20 changes: 18 additions & 2 deletions app/models/fact_check.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class FactCheck < ApplicationRecord
include ClaimAndFactCheck
include Article

attr_accessor :skip_report_update, :publish_report

Expand Down Expand Up @@ -32,7 +32,7 @@ def set_language
def language_in_allowed_values
allowed_languages = self.project_media&.team&.get_languages || ['en']
allowed_languages << 'und'
errors.add(:language, I18n.t(:"errors.messages.invalid_fact_check_language_value")) unless allowed_languages.include?(self.language)
errors.add(:language, I18n.t(:"errors.messages.invalid_article_language_value")) unless allowed_languages.include?(self.language)
end

def title_or_summary_exists
Expand Down Expand Up @@ -76,4 +76,20 @@ def update_report
reports.skip_check_ability = true
reports.save!
end

def article_elasticsearch_data(action = 'create_or_update')
return if self.disable_es_callbacks || RequestStore.store[:disable_es_callbacks]
data = action == 'destroy' ? {
'fact_check_title' => '',
'fact_check_summary' => '',
'fact_check_url' => '',
'fact_check_languages' => []
} : {
'fact_check_title' => self.title,
'fact_check_summary' => self.summary,
'fact_check_url' => self.url,
'fact_check_languages' => [self.language]
}
self.index_in_elasticsearch(data)
end
end
2 changes: 1 addition & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ en:
invalid_project_media_channel_value: Sorry, channel value not supported
invalid_project_media_channel_update: Sorry, you could not update channel value
invalid_project_media_archived_value: Sorry, archived value not supported
invalid_fact_check_language_value: Sorry, language value not supported
invalid_article_language_value: Sorry, language value not supported
fact_check_empty_title_and_summary: Sorry, you should fill title or summary
invalid_feed_saved_search_value: should belong to a workspace that is part of this feed
platform_allowed_values_error: 'cannot be of type %{type}, allowed types: %{allowed_types}'
Expand Down
14 changes: 14 additions & 0 deletions db/migrate/20240417140727_create_explainers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class CreateExplainers < ActiveRecord::Migration[6.1]
def change
create_table :explainers do |t|
t.string :title
t.text :description
t.string :url
t.string :language
t.references :user, foreign_key: true, null: false
t.references :team, foreign_key: true, null: false

t.timestamps
end
end
end
19 changes: 17 additions & 2 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_04_04_154458) do
ActiveRecord::Schema.define(version: 2024_04_17_140727) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -285,7 +285,7 @@
t.jsonb "value_json", default: "{}"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index "dynamic_annotation_fields_value(field_name, value)", name: "dynamic_annotation_fields_value", where: "((field_name)::text = ANY ((ARRAY['external_id'::character varying, 'smooch_user_id'::character varying, 'verification_status_status'::character varying])::text[]))"
t.index "dynamic_annotation_fields_value(field_name, value)", name: "dynamic_annotation_fields_value", where: "((field_name)::text = ANY (ARRAY[('external_id'::character varying)::text, ('smooch_user_id'::character varying)::text, ('verification_status_status'::character varying)::text]))"
t.index ["annotation_id", "field_name"], name: "index_dynamic_annotation_fields_on_annotation_id_and_field_name"
t.index ["annotation_id"], name: "index_dynamic_annotation_fields_on_annotation_id"
t.index ["annotation_type"], name: "index_dynamic_annotation_fields_on_annotation_type"
Expand All @@ -298,6 +298,19 @@
t.index ["value_json"], name: "index_dynamic_annotation_fields_on_value_json", using: :gin
end

create_table "explainers", force: :cascade do |t|
t.string "title"
t.text "description"
t.string "url"
t.string "language"
t.bigint "user_id", null: false
t.bigint "team_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["team_id"], name: "index_explainers_on_team_id"
t.index ["user_id"], name: "index_explainers_on_user_id"
end

create_table "fact_checks", force: :cascade do |t|
t.text "summary"
t.string "url"
Expand Down Expand Up @@ -885,6 +898,8 @@

add_foreign_key "claim_descriptions", "project_medias"
add_foreign_key "claim_descriptions", "users"
add_foreign_key "explainers", "teams"
add_foreign_key "explainers", "users"
add_foreign_key "fact_checks", "claim_descriptions"
add_foreign_key "fact_checks", "users"
add_foreign_key "feed_invitations", "feeds"
Expand Down
18 changes: 17 additions & 1 deletion db/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,10 @@ def saved_searches
teams.each_value { |team| saved_search(team) }
end

def explainers
teams.each_value { |team| 5.times { create_explainer(team) } }
end

def main_user_feed(to_be_shared)
if to_be_shared == "share_factchecks"
data_points = [1]
Expand Down Expand Up @@ -390,7 +394,7 @@ def suggest_relationships

def tipline_requests
teams_project_medias.each_value do |team_project_medias|
create_tipline_requests(team_project_medias)
create_tipline_requests(team_project_medias)
end
end

Expand Down Expand Up @@ -481,6 +485,16 @@ def saved_search(team)
end
end

def create_explainer(team)
Explainer.create!({
title: Faker::Lorem.sentence,
url: random_url,
description: Faker::Lorem.paragraph(sentence_count: 8),
team: team,
user: users[:main_user_a],
})
end

def feed_invitation(feed, invited_user)
feed_invitation_params = {
email: invited_user.email,
Expand Down Expand Up @@ -703,6 +717,8 @@ def cluster(project_media, feed, team_id)
populated_workspaces.publish_fact_checks
puts 'Creating Clusters'
populated_workspaces.clusters(feed_2)
puts 'Creating Explainers'
populated_workspaces.explainers
rescue RuntimeError => e
if e.message.include?('We could not parse this link')
puts "—————"
Expand Down
Loading

0 comments on commit 2cfd4f7

Please sign in to comment.