diff --git a/app/graph/mutations/claim_description_mutations.rb b/app/graph/mutations/claim_description_mutations.rb index 06eeba4525..dc8a9ac977 100644 --- a/app/graph/mutations/claim_description_mutations.rb +++ b/app/graph/mutations/claim_description_mutations.rb @@ -8,13 +8,12 @@ module SharedCreateAndUpdateFields included do argument :description, GraphQL::Types::String, required: false argument :context, GraphQL::Types::String, required: false, as: :claim_context + argument :project_media_id, GraphQL::Types::Int, required: false, camelize: false end end class Create < Mutations::CreateMutation include SharedCreateAndUpdateFields - - argument :project_media_id, GraphQL::Types::Int, required: true, camelize: false end class Update < Mutations::UpdateMutation diff --git a/app/graph/mutations/explainer_item_mutations.rb b/app/graph/mutations/explainer_item_mutations.rb new file mode 100644 index 0000000000..eb07e5ca9d --- /dev/null +++ b/app/graph/mutations/explainer_item_mutations.rb @@ -0,0 +1,11 @@ +module ExplainerItemMutations + MUTATION_TARGET = 'explainer_item'.freeze + PARENTS = ['explainer', 'project_media'].freeze + + class Create < Mutations::CreateMutation + argument :explainer_id, GraphQL::Types::Int, required: true + argument :project_media_id, GraphQL::Types::Int, required: true + end + + class Destroy < Mutations::DestroyMutation; end +end diff --git a/app/graph/mutations/explainer_mutations.rb b/app/graph/mutations/explainer_mutations.rb index 7856561994..03c217c2e2 100644 --- a/app/graph/mutations/explainer_mutations.rb +++ b/app/graph/mutations/explainer_mutations.rb @@ -6,10 +6,11 @@ module SharedCreateAndUpdateFields extend ActiveSupport::Concern included do - argument :title, GraphQL::Types::String, required: true + argument :title, GraphQL::Types::String, required: false argument :description, GraphQL::Types::String, required: false argument :url, GraphQL::Types::String, required: false argument :language, GraphQL::Types::String, required: false + argument :tags, [GraphQL::Types::String, null: true], required: false end end diff --git a/app/graph/mutations/fact_check_mutations.rb b/app/graph/mutations/fact_check_mutations.rb index 8d1f5f4509..b6378feabd 100644 --- a/app/graph/mutations/fact_check_mutations.rb +++ b/app/graph/mutations/fact_check_mutations.rb @@ -1,6 +1,6 @@ module FactCheckMutations MUTATION_TARGET = 'fact_check'.freeze - PARENTS = ['claim_description'].freeze + PARENTS = ['claim_description', 'team'].freeze module SharedCreateAndUpdateFields extend ActiveSupport::Concern @@ -8,6 +8,8 @@ module SharedCreateAndUpdateFields included do argument :url, GraphQL::Types::String, required: false argument :language, GraphQL::Types::String, required: false + argument :tags, [GraphQL::Types::String, null: true], required: false + argument :rating, GraphQL::Types::String, required: false end end diff --git a/app/graph/types/article_union.rb b/app/graph/types/article_union.rb index 58a7e7479a..b1857b12ec 100644 --- a/app/graph/types/article_union.rb +++ b/app/graph/types/article_union.rb @@ -2,5 +2,6 @@ class ArticleUnion < BaseUnion description 'A union type of all article types we can handle' possible_types( ExplainerType, + FactCheckType, ) end diff --git a/app/graph/types/claim_description_type.rb b/app/graph/types/claim_description_type.rb index 7871e02e2e..7468b3f44a 100644 --- a/app/graph/types/claim_description_type.rb +++ b/app/graph/types/claim_description_type.rb @@ -14,7 +14,7 @@ class ClaimDescriptionType < DefaultObject def fact_check(report_status: nil) ability = context[:ability] || Ability.new - status = object.project_media.report_status + status = object.project_media&.report_status can_read = ability.can?(:read, object) || status == 'published' (can_read && (!report_status || status == report_status)) ? object.fact_check : nil end diff --git a/app/graph/types/explainer_item_type.rb b/app/graph/types/explainer_item_type.rb new file mode 100644 index 0000000000..81e3d194bf --- /dev/null +++ b/app/graph/types/explainer_item_type.rb @@ -0,0 +1,10 @@ +class ExplainerItemType < DefaultObject + description 'Explainer item type' + + implements GraphQL::Types::Relay::Node + + field :explainer_id, GraphQL::Types::Int, null: false + field :project_media_id, GraphQL::Types::Int, null: false + field :explainer, ExplainerType, null: false + field :project_media, ProjectMediaType, null: false +end diff --git a/app/graph/types/explainer_type.rb b/app/graph/types/explainer_type.rb index dea3441e0c..c27bcf75c9 100644 --- a/app/graph/types/explainer_type.rb +++ b/app/graph/types/explainer_type.rb @@ -1,5 +1,5 @@ class ExplainerType < DefaultObject - description "Explainer type" + description 'Explainer type' implements GraphQL::Types::Relay::Node @@ -12,10 +12,5 @@ class ExplainerType < DefaultObject 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 + field :tags, [GraphQL::Types::String, null: true], null: true end diff --git a/app/graph/types/fact_check_type.rb b/app/graph/types/fact_check_type.rb index 3cd177d458..0befd82e5b 100644 --- a/app/graph/types/fact_check_type.rb +++ b/app/graph/types/fact_check_type.rb @@ -10,4 +10,8 @@ class FactCheckType < DefaultObject field :language, GraphQL::Types::String, null: true field :user, UserType, null: true field :claim_description, ClaimDescriptionType, null: true + field :tags, [GraphQL::Types::String, null: true], null: true + field :rating, GraphQL::Types::String, null: true + field :imported, GraphQL::Types::Boolean, null: true + field :report_status, GraphQL::Types::String, null: true end diff --git a/app/graph/types/mutation_type.rb b/app/graph/types/mutation_type.rb index 63be2053c6..a87ea5528c 100644 --- a/app/graph/types/mutation_type.rb +++ b/app/graph/types/mutation_type.rb @@ -149,4 +149,7 @@ class MutationType < BaseObject field :createApiKey, mutation: ApiKeyMutations::Create field :destroyApiKey, mutation: ApiKeyMutations::Destroy + + field :createExplainerItem, mutation: ExplainerItemMutations::Create + field :destroyExplainerItem, mutation: ExplainerItemMutations::Destroy end diff --git a/app/graph/types/project_media_type.rb b/app/graph/types/project_media_type.rb index eed899f195..b04f78ad7c 100644 --- a/app/graph/types/project_media_type.rb +++ b/app/graph/types/project_media_type.rb @@ -7,6 +7,7 @@ class ProjectMediaType < DefaultObject field :media_id, GraphQL::Types::Int, null: true field :user_id, GraphQL::Types::Int, null: true + field :fact_check_id, GraphQL::Types::Int, null: true field :url, GraphQL::Types::String, null: true field :full_url, GraphQL::Types::String, null: true field :quote, GraphQL::Types::String, null: true @@ -370,4 +371,18 @@ def is_secondary field :similar_items, ProjectMediaType.connection_type, null: true field :media_slug, GraphQL::Types::String, null: true + + field :fact_check, FactCheckType, null: true + + field :explainers, ExplainerType.connection_type, null: true + + field :explainer_items, ExplainerItemType.connection_type, null: true + + field :articles_count, GraphQL::Types::Int, null: true + + def articles_count + count = object.explainers.count + count += 1 if object.fact_check + count + end end diff --git a/app/graph/types/team_type.rb b/app/graph/types/team_type.rb index 329b272c6c..bfecbefebd 100644 --- a/app/graph/types/team_type.rb +++ b/app/graph/types/team_type.rb @@ -288,10 +288,64 @@ def tipline_messages(uid:) 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' + # Sort and pagination + argument :offset, GraphQL::Types::Int, required: false, default_value: 0 + argument :sort, GraphQL::Types::String, required: false, default_value: 'title' + argument :sort_type, GraphQL::Types::String, required: false, camelize: false, default_value: 'ASC' + + # Filters + argument :user_ids, [GraphQL::Types::Int, null: true], required: false, camelize: false + argument :tags, [GraphQL::Types::String, null: true], required: false, camelize: false + argument :language, [GraphQL::Types::String, null: true], required: false, camelize: false + argument :updated_at, GraphQL::Types::String, required: false, camelize: false # JSON + argument :text, GraphQL::Types::String, required: false, camelize: false # Search by text + argument :standalone, GraphQL::Types::Boolean, required: false, camelize: false # Not applied to any item (fact-checks only) + argument :publisher_ids, [GraphQL::Types::Int, null: true], required: false, camelize: false + argument :report_status, [GraphQL::Types::String, null: true], required: false, camelize: false + argument :rating, [GraphQL::Types::String, null: true], required: false, camelize: false + argument :imported, GraphQL::Types::Boolean, required: false, camelize: false # Only for fact-checks + end + + def articles(**args) + sort = args[:sort].to_s + order = [:title, :language, :updated_at, :id].include?(sort.downcase.to_sym) ? sort.downcase.to_sym : :title + order_type = args[:sort_type].to_s.downcase.to_sym == :desc ? :desc : :asc + articles = Explainer.none + if args[:article_type] == 'explainer' + articles = object.filtered_explainers(args) + elsif args[:article_type] == 'fact-check' + articles = object.filtered_fact_checks(args) + end + articles.offset(args[:offset].to_i).order(order => order_type) + end + + field :articles_count, GraphQL::Types::Int, null: true do + argument :article_type, GraphQL::Types::String, required: false, camelize: false + + # Filters + argument :user_ids, [GraphQL::Types::Int, null: true], required: false, camelize: false + argument :tags, [GraphQL::Types::String, null: true], required: false, camelize: false + argument :language, [GraphQL::Types::String, null: true], required: false, camelize: false + argument :updated_at, GraphQL::Types::String, required: false, camelize: false # JSON + argument :text, GraphQL::Types::String, required: false, camelize: false # Search by text + argument :standalone, GraphQL::Types::Boolean, required: false, camelize: false # Not applied to any item (fact-checks only) + argument :publisher_ids, [GraphQL::Types::Int, null: true], required: false, camelize: false + argument :report_status, [GraphQL::Types::String, null: true], required: false, camelize: false + argument :rating, [GraphQL::Types::String, null: true], required: false, camelize: false + argument :imported, GraphQL::Types::Boolean, required: false, camelize: false # Only for fact-checks + end + + def articles_count(**args) + count = nil + if args[:article_type] == 'explainer' + count = object.filtered_explainers(args).count + elsif args[:article_type] == 'fact-check' + count = object.filtered_fact_checks(args).count + elsif args[:article_type].blank? + count = object.filtered_explainers(args).count + object.filtered_fact_checks(args).count + end + count end field :api_key, ApiKeyType, null: true do diff --git a/app/models/ability.rb b/app/models/ability.rb index 825d05d775..42d45f7fa6 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -170,9 +170,10 @@ def collaborator_perms v_obj = obj.item_type.constantize.find(obj.item_id) if obj.item_type == 'ProjectMedia' !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], FactCheck, { claim_description: { 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 } } + can [:create, :update, :read], ClaimDescription, { team_id: @context_team.id } + can [:create, :update, :read, :destroy], ExplainerItem, { project_media: { team_id: @context_team.id } } end def bot_permissions diff --git a/app/models/claim_description.rb b/app/models/claim_description.rb index 28e73e3cc9..bf2dccd068 100644 --- a/app/models/claim_description.rb +++ b/app/models/claim_description.rb @@ -1,13 +1,16 @@ class ClaimDescription < ApplicationRecord include Article - belongs_to :project_media + before_validation :set_team, on: :create + belongs_to :project_media, optional: true + belongs_to :team has_one :fact_check, dependent: :destroy accepts_nested_attributes_for :fact_check, reject_if: proc { |attributes| attributes['summary'].blank? } - validates_presence_of :project_media - validates_uniqueness_of :project_media_id + validates_presence_of :team + validates_uniqueness_of :project_media_id, allow_nil: true + after_commit :update_fact_check, on: [:update] # To avoid GraphQL conflict with name `context` alias_attribute :claim_context, :context @@ -32,4 +35,19 @@ def article_elasticsearch_data(action = 'create_or_update') } self.index_in_elasticsearch(data) end + + private + + def set_team + team = (self.project_media&.team || Team.current) + self.team = team unless team.nil? + end + + def update_fact_check + fact_check = self.fact_check + if fact_check && self.project_media_id + fact_check.updated_at = Time.now + fact_check.save! + end + end end diff --git a/app/models/concerns/article.rb b/app/models/concerns/article.rb index 854fb6ca81..f2c11b2b18 100644 --- a/app/models/concerns/article.rb +++ b/app/models/concerns/article.rb @@ -15,6 +15,7 @@ module Article after_commit :update_elasticsearch_data, :send_to_alegre, :notify_bots, on: [:create, :update] after_commit :destroy_elasticsearch_data, on: :destroy + after_save :create_tag_texts_if_needed end def text_fields @@ -46,7 +47,7 @@ def notify_bots 'ClaimDescription' => 'save_claim_description', 'FactCheck' => 'save_fact_check' }[self.class.name] - BotUser.enqueue_event(event, self.project_media.team_id, self) + BotUser.enqueue_event(event, self.project_media.team_id, self) unless self.project_media.nil? end protected @@ -54,6 +55,7 @@ def notify_bots def index_in_elasticsearch(data) # touch project media to update `updated_at` date pm = self.project_media + return if pm.nil? pm = ProjectMedia.find_by_id(pm.id) unless pm.nil? updated_at = Time.now @@ -64,9 +66,25 @@ def index_in_elasticsearch(data) end end + def create_tag_texts_if_needed + self.class.delay.create_tag_texts_if_needed(self.team_id, self.tags) if self.respond_to?(:tags) && !self.tags.blank? + end + module ClassMethods + def create_tag_texts_if_needed(team_id, tags) + tags.each do |tag| + next if TagText.where(text: tag, team_id: team_id).exists? + tag_text = TagText.new + tag_text.text = tag + tag_text.team_id = team_id + tag_text.skip_check_ability = true + tag_text.save! + end + end + def send_to_alegre(id) obj = self.find_by_id(id) + return if obj.project_media.nil? obj.text_fields.each do |field| ::Bot::Alegre.send_field_to_similarity_index(obj.project_media, field) end unless obj.nil? diff --git a/app/models/concerns/project_media_associations.rb b/app/models/concerns/project_media_associations.rb index 5f9ca5c1b1..ca52f8f980 100644 --- a/app/models/concerns/project_media_associations.rb +++ b/app/models/concerns/project_media_associations.rb @@ -21,6 +21,8 @@ module ProjectMediaAssociations has_one :claim_description, dependent: :destroy belongs_to :source, optional: true has_many :tipline_requests, as: :associated + has_many :explainer_items + has_many :explainers, through: :explainer_items has_annotations end end diff --git a/app/models/concerns/project_media_cached_fields.rb b/app/models/concerns/project_media_cached_fields.rb index 6f5c966c42..233bfb1bac 100644 --- a/app/models/concerns/project_media_cached_fields.rb +++ b/app/models/concerns/project_media_cached_fields.rb @@ -73,7 +73,8 @@ def title_or_description_update model: FactCheck, affected_ids: proc { |fc| [fc.claim_description.project_media] }, events: { - save: :recalculate + save: :recalculate, + destroy: :recalculate } } @@ -176,6 +177,11 @@ def title_or_description_update } ] + cached_field :fact_check_id, + start_as: nil, + recalculate: :recalculate_fact_check_id, + update_on: [FACT_CHECK_EVENT] + cached_field :fact_check_title, start_as: nil, recalculate: :recalculate_fact_check_title, @@ -540,6 +546,10 @@ def recalculate_last_seen [v1, v2].max.to_i end + def recalculate_fact_check_id + self.claim_description&.fact_check&.id + end + def recalculate_fact_check_title self.claim_description&.fact_check&.title end diff --git a/app/models/concerns/project_media_getters.rb b/app/models/concerns/project_media_getters.rb index ba7af18797..7770fda59f 100644 --- a/app/models/concerns/project_media_getters.rb +++ b/app/models/concerns/project_media_getters.rb @@ -209,4 +209,8 @@ def get_creator_name def team_avatar self.team.avatar end + + def fact_check + self.claim_description&.fact_check + end end diff --git a/app/models/concerns/team_associations.rb b/app/models/concerns/team_associations.rb index 5d0cbb5adc..83cf62fa42 100644 --- a/app/models/concerns/team_associations.rb +++ b/app/models/concerns/team_associations.rb @@ -116,4 +116,8 @@ def check_search_unconfirmed def check_search_spam check_search_filter({ 'archived' => CheckArchivedFlags::FlagCodes::SPAM }) end + + def fact_checks + FactCheck.joins(:claim_description).where('claim_descriptions.team_id' => self.id) + end end diff --git a/app/models/explainer.rb b/app/models/explainer.rb index 52a01f214f..20dde8aa2f 100644 --- a/app/models/explainer.rb +++ b/app/models/explainer.rb @@ -4,10 +4,12 @@ class Explainer < ApplicationRecord belongs_to :team has_annotations + has_many :explainer_items + has_many :project_medias, through: :explainer_items before_validation :set_team validates_format_of :url, with: URI.regexp, allow_blank: true, allow_nil: true - validates_presence_of :team + validates_presence_of :team, :title, :description validate :language_in_allowed_values, unless: proc { |e| e.language.blank? } def notify_bots diff --git a/app/models/explainer_item.rb b/app/models/explainer_item.rb new file mode 100644 index 0000000000..114ecb3347 --- /dev/null +++ b/app/models/explainer_item.rb @@ -0,0 +1,14 @@ +# Join model +class ExplainerItem < ApplicationRecord + belongs_to :explainer + belongs_to :project_media + + validates_presence_of :explainer, :project_media + validate :same_team + + private + + def same_team + errors.add(:base, I18n.t(:explainer_and_item_must_be_from_the_same_team)) unless self.explainer&.team_id == self.project_media&.team_id + end +end diff --git a/app/models/fact_check.rb b/app/models/fact_check.rb index 2a3e7ac2b8..3c4f559fc5 100644 --- a/app/models/fact_check.rb +++ b/app/models/fact_check.rb @@ -1,18 +1,23 @@ class FactCheck < ApplicationRecord include Article + enum report_status: { unpublished: 0, published: 1, paused: 2 } + attr_accessor :skip_report_update, :publish_report belongs_to :claim_description + before_validation :set_initial_rating, on: :create, if: proc { |fc| fc.rating.blank? && fc.claim_description.present? } before_validation :set_language, on: :create, if: proc { |fc| fc.language.blank? } + before_validation :set_imported, on: :create validates_presence_of :claim_description validates_uniqueness_of :claim_description_id validates_format_of :url, with: URI.regexp, allow_blank: true, allow_nil: true - validate :language_in_allowed_values, :title_or_summary_exists + validate :language_in_allowed_values, :title_or_summary_exists, :rating_in_allowed_values - after_save :update_report + after_save :update_report, unless: proc { |fc| fc.skip_report_update || !DynamicAnnotation::AnnotationType.where(annotation_type: 'report_design').exists? || fc.project_media.blank? } + after_save :update_item_status, if: proc { |fc| fc.saved_change_to_rating? } def text_fields ['fact_check_title', 'fact_check_summary'] @@ -22,25 +27,44 @@ def project_media self.claim_description&.project_media end + def team_id + self.claim_description&.team_id + end + + def team + self.claim_description&.team + end + private def set_language - languages = self.project_media&.team&.get_languages || ['en'] + languages = self.claim_description&.team&.get_languages || ['en'] self.language = languages.length == 1 ? languages.first : 'und' end + def set_imported + self.imported = true if self.user&.type == 'BotUser' # We consider "imported" the fact-checks that are not created by humans inside Check + end + def language_in_allowed_values - allowed_languages = self.project_media&.team&.get_languages || ['en'] + allowed_languages = self.claim_description&.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 + def rating_in_allowed_values + unless self.rating.blank? + team = self.claim_description.team + allowed_statuses = team.verification_statuses('media', nil)['statuses'].collect{ |s| s[:id] } + errors.add(:rating, I18n.t(:workflow_status_is_not_valid, status: self.rating, valid: allowed_statuses.join(', '))) unless allowed_statuses.include?(self.rating) + end + end + def title_or_summary_exists errors.add(:base, I18n.t(:"errors.messages.fact_check_empty_title_and_summary")) if self.title.blank? && self.summary.blank? end def update_report - return if self.skip_report_update || !DynamicAnnotation::AnnotationType.where(annotation_type: 'report_design').exists? pm = self.project_media reports = pm.get_dynamic_annotation('report_design') || Dynamic.new(annotation_type: 'report_design', annotated: pm) data = reports.data.to_h.with_indifferent_access @@ -78,6 +102,16 @@ def update_report reports.save! end + def update_item_status + pm = self.project_media + s = pm&.last_status_obj + if !s.nil? && s.status != self.rating + s.skip_check_ability = true + s.status = self.rating + s.save! + end + end + def article_elasticsearch_data(action = 'create_or_update') return if self.disable_es_callbacks || RequestStore.store[:disable_es_callbacks] data = action == 'destroy' ? { @@ -93,4 +127,10 @@ def article_elasticsearch_data(action = 'create_or_update') } self.index_in_elasticsearch(data) end + + def set_initial_rating + pm_rating = self.project_media&.last_status + default_rating = self.claim_description.team.verification_statuses('media', nil)['default'] + self.rating = pm_rating || default_rating + end end diff --git a/app/models/team.rb b/app/models/team.rb index 349cb569f8..979e975366 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -3,6 +3,7 @@ class Team < ApplicationRecord after_create :create_team_partition before_destroy :delete_created_bots, :remove_is_default_project_flag + include SearchHelper include ValidationsHelper include DestroyLater include TeamValidations @@ -481,6 +482,60 @@ def available_newsletter_header_types available end + def filtered_explainers(filters = {}) + query = self.explainers + + # Filter by tags + query = query.where('ARRAY[?]::varchar[] && tags', filters[:tags].to_a.map(&:to_s)) unless filters[:tags].blank? + + # Filter by user + query = query.where(user_id: filters[:user_ids].to_a.map(&:to_i)) unless filters[:user_ids].blank? + + # Filter by date + query = query.where(updated_at: Range.new(*format_times_search_range_filter(JSON.parse(filters[:updated_at]), nil))) unless filters[:updated_at].blank? + + # Filter by text + query = query.where('(title ILIKE ? OR url ILIKE ? OR description ILIKE ?)', *["%#{filters[:text]}%"]*3) if filters[:text].to_s.size > 2 + + query + end + + def filtered_fact_checks(filters = {}) + query = FactCheck.includes(:claim_description).where('claim_descriptions.team_id' => self.id) + + # Filter by standalone + query = query.where('claim_descriptions.project_media_id' => nil) if filters[:standalone] + + # Filter by language + query = query.where('fact_checks.language' => filters[:language].to_a) unless filters[:language].blank? + + # Filter by tags + query = query.where('ARRAY[?]::varchar[] && fact_checks.tags', filters[:tags].to_a.map(&:to_s)) unless filters[:tags].blank? + + # Filter by user + query = query.where('fact_checks.user_id' => filters[:user_ids].to_a.map(&:to_i)) unless filters[:user_ids].blank? + + # Filter by date + query = query.where('fact_checks.updated_at' => Range.new(*format_times_search_range_filter(JSON.parse(filters[:updated_at]), nil))) unless filters[:updated_at].blank? + + # Filter by publisher + query = query.where('fact_checks.publisher_id' => filters[:publisher_ids].to_a.map(&:to_i)) unless filters[:publisher_ids].blank? + + # Filter by rating + query = query.where('fact_checks.rating' => filters[:rating].to_a.map(&:to_s)) unless filters[:rating].blank? + + # Filter by imported + query = query.where('fact_checks.imported' => !!filters[:imported]) unless filters[:imported].nil? + + # Filter by report status + query = query.where('fact_checks.report_status' => filters[:report_status].to_a.map(&:to_s)) unless filters[:report_status].blank? + + # Filter by text + query = query.where('(title ILIKE ? OR url ILIKE ? OR summary ILIKE ?)', *["%#{filters[:text]}%"]*3) if filters[:text].to_s.size > 2 + + query + end + # private # # Please add private methods to app/models/concerns/team_private.rb diff --git a/app/models/workflow/verification_status.rb b/app/models/workflow/verification_status.rb index 7ff6a7ae08..3471014481 100644 --- a/app/models/workflow/verification_status.rb +++ b/app/models/workflow/verification_status.rb @@ -6,7 +6,6 @@ class Workflow::VerificationStatus < Workflow::Base check_workflow on: :create, actions: :index_on_es_background check_workflow on: :update, actions: :index_on_es_foreground - def self.core_default_value 'undetermined' end @@ -109,6 +108,14 @@ def update_report_design_if_needed }) report.data = data report.save! + # update FactCheck rating + fc = pm&.claim_description&.fact_check + if !fc.nil? && fc.rating != self.value + fc.skip_report_update = true + fc.skip_check_ability = true + fc.rating = self.value + fc.save! + end end end end diff --git a/config/initializers/report_designer.rb b/config/initializers/report_designer.rb index 79743c8b80..9a36a9282b 100644 --- a/config/initializers/report_designer.rb +++ b/config/initializers/report_designer.rb @@ -21,7 +21,17 @@ user = self.annotator || User.current url = self.report_design_field_value('published_article_url') language = self.report_design_field_value('language') - fields = { user: user, skip_report_update: true , url: url, language: language } + state = self.data['state'] + publisher_id = state == 'published' ? self.annotator_id : nil + fields = { + user: user, + skip_report_update: true, + url: url, + language: language, + publisher_id: publisher_id, + report_status: state, + rating: pm.status + } if self.report_design_field_value('use_text_message') title = self.report_design_field_value('title') summary = self.report_design_field_value('text') @@ -51,6 +61,23 @@ Feed.delay_for(1.minute, retry: 0).notify_subscribers(pm, title, summary, url) Request.delay_for(1.minute, retry: 0).update_fact_checked_by(pm) end + + if self.annotation_type == 'report_design' && self.action =~ /pause/ + # Update report fields + fc = pm&.claim_description&.fact_check + unless fc.nil? + state = self.data['state'] + fields = { + skip_report_update: true, + publisher_id: nil, + report_status: state, + rating: pm.status + } + fields.each { |field, value| fc.send("#{field}=", value) } + fc.skip_check_ability = true + fc.save! + end + end end def report_design_introduction(data, language) diff --git a/config/locales/en.yml b/config/locales/en.yml index 364f6241f5..aa441378fc 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -822,6 +822,7 @@ en: send_every_must_be_a_list_of_days_of_the_week: must be a list of days of the week. send_on_must_be_in_the_future: can't be in the past. cant_delete_default_folder: The default folder can't be deleted + explainer_and_item_must_be_from_the_same_team: Explainer and item must be from the same workspace. shared_feed_imported_media_already_exist: |- No media eligible to be imported into your workspace. The media selected to import already exist in your workspace in the following items: diff --git a/db/migrate/20240527011635_add_tags_to_explainers.rb b/db/migrate/20240527011635_add_tags_to_explainers.rb new file mode 100644 index 0000000000..c6e2d33e5b --- /dev/null +++ b/db/migrate/20240527011635_add_tags_to_explainers.rb @@ -0,0 +1,6 @@ +class AddTagsToExplainers < ActiveRecord::Migration[6.1] + def change + add_column :explainers, :tags, :string, array: true, default: [] + add_index :explainers, :tags, using: 'gin' + end +end diff --git a/db/migrate/20240528170336_add_tags_to_fact_checks.rb b/db/migrate/20240528170336_add_tags_to_fact_checks.rb new file mode 100644 index 0000000000..dbb9648a96 --- /dev/null +++ b/db/migrate/20240528170336_add_tags_to_fact_checks.rb @@ -0,0 +1,6 @@ +class AddTagsToFactChecks < ActiveRecord::Migration[6.1] + def change + add_column :fact_checks, :tags, :string, array: true, default: [] + add_index :fact_checks, :tags, using: 'gin' + end +end diff --git a/db/migrate/20240604045337_add_fields_to_fact_check.rb b/db/migrate/20240604045337_add_fields_to_fact_check.rb new file mode 100644 index 0000000000..fdca5e908f --- /dev/null +++ b/db/migrate/20240604045337_add_fields_to_fact_check.rb @@ -0,0 +1,10 @@ +class AddFieldsToFactCheck < ActiveRecord::Migration[6.1] + def change + add_column :fact_checks, :publisher_id, :integer, null: true + add_column :fact_checks, :report_status, :integer, null: true, default: 0 + add_column :fact_checks, :rating, :string, null: true + add_index :fact_checks, :publisher_id + add_index :fact_checks, :report_status + add_index :fact_checks, :rating + end +end diff --git a/db/migrate/20240613005052_create_explainer_items.rb b/db/migrate/20240613005052_create_explainer_items.rb new file mode 100644 index 0000000000..d5d658a3ff --- /dev/null +++ b/db/migrate/20240613005052_create_explainer_items.rb @@ -0,0 +1,10 @@ +class CreateExplainerItems < ActiveRecord::Migration[6.1] + def change + create_table :explainer_items do |t| + t.references :explainer, foreign_key: true + t.references :project_media, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20240619131714_add_team_id_to_claim_descriptions.rb b/db/migrate/20240619131714_add_team_id_to_claim_descriptions.rb new file mode 100644 index 0000000000..59221fbbc8 --- /dev/null +++ b/db/migrate/20240619131714_add_team_id_to_claim_descriptions.rb @@ -0,0 +1,6 @@ +class AddTeamIdToClaimDescriptions < ActiveRecord::Migration[6.1] + def change + add_reference :claim_descriptions, :team, index: true + change_column_null :claim_descriptions, :project_media_id, true + end +end diff --git a/db/migrate/20240713012502_add_imported_to_fact_checks.rb b/db/migrate/20240713012502_add_imported_to_fact_checks.rb new file mode 100644 index 0000000000..83474c2d6c --- /dev/null +++ b/db/migrate/20240713012502_add_imported_to_fact_checks.rb @@ -0,0 +1,6 @@ +class AddImportedToFactChecks < ActiveRecord::Migration[6.1] + def change + add_column :fact_checks, :imported, :boolean, default: false + add_index :fact_checks, :imported + end +end diff --git a/db/migrate/20240714051039_add_unique_index_to_explainer_items.rb b/db/migrate/20240714051039_add_unique_index_to_explainer_items.rb new file mode 100644 index 0000000000..2778977a6a --- /dev/null +++ b/db/migrate/20240714051039_add_unique_index_to_explainer_items.rb @@ -0,0 +1,5 @@ +class AddUniqueIndexToExplainerItems < ActiveRecord::Migration[6.1] + def change + add_index :explainer_items, [:explainer_id, :project_media_id], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 33471ff28f..2ba6278e80 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -216,11 +216,13 @@ create_table "claim_descriptions", force: :cascade do |t| t.text "description" t.bigint "user_id", null: false - t.bigint "project_media_id", null: false + t.bigint "project_media_id" t.text "context" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "team_id" t.index ["project_media_id"], name: "index_claim_descriptions_on_project_media_id", unique: true + t.index ["team_id"], name: "index_claim_descriptions_on_team_id" t.index ["user_id"], name: "index_claim_descriptions_on_user_id" end @@ -289,7 +291,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)::text, ('smooch_user_id'::character varying)::text, ('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, 'smooch_user_id'::character varying, '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" @@ -302,6 +304,16 @@ t.index ["value_json"], name: "index_dynamic_annotation_fields_on_value_json", using: :gin end + create_table "explainer_items", force: :cascade do |t| + t.bigint "explainer_id" + t.bigint "project_media_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["explainer_id", "project_media_id"], name: "index_explainer_items_on_explainer_id_and_project_media_id", unique: true + t.index ["explainer_id"], name: "index_explainer_items_on_explainer_id" + t.index ["project_media_id"], name: "index_explainer_items_on_project_media_id" + end + create_table "explainers", force: :cascade do |t| t.string "title" t.text "description" @@ -311,6 +323,8 @@ t.bigint "team_id", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.string "tags", default: [], array: true + t.index ["tags"], name: "index_explainers_on_tags", using: :gin t.index ["team_id"], name: "index_explainers_on_team_id" t.index ["user_id"], name: "index_explainers_on_user_id" end @@ -325,9 +339,19 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "signature" + t.string "tags", default: [], array: true + t.integer "publisher_id" + t.integer "report_status", default: 0 + t.string "rating" + t.boolean "imported", default: false t.index ["claim_description_id"], name: "index_fact_checks_on_claim_description_id", unique: true + t.index ["imported"], name: "index_fact_checks_on_imported" t.index ["language"], name: "index_fact_checks_on_language" + t.index ["publisher_id"], name: "index_fact_checks_on_publisher_id" + t.index ["rating"], name: "index_fact_checks_on_rating" + t.index ["report_status"], name: "index_fact_checks_on_report_status" t.index ["signature"], name: "index_fact_checks_on_signature", unique: true + t.index ["tags"], name: "index_fact_checks_on_tags", using: :gin t.index ["user_id"], name: "index_fact_checks_on_user_id" end @@ -486,7 +510,7 @@ t.index ["user_id"], name: "index_project_media_users_on_user_id" end - create_table "project_medias", force: :cascade do |t| + create_table "project_medias", id: :serial, force: :cascade do |t| t.integer "project_id" t.integer "media_id" t.integer "user_id" @@ -881,7 +905,8 @@ end create_table "versions", id: :serial, force: :cascade do |t| - t.string "item_type", null: false + t.string "item_type" + t.string "{:null=>false}" t.string "item_id", null: false t.string "event", null: false t.string "whodunnit" @@ -902,6 +927,8 @@ add_foreign_key "claim_descriptions", "project_medias" add_foreign_key "claim_descriptions", "users" + add_foreign_key "explainer_items", "explainers" + add_foreign_key "explainer_items", "project_medias" add_foreign_key "explainers", "teams" add_foreign_key "explainers", "users" add_foreign_key "fact_checks", "claim_descriptions" diff --git a/db/seeds.rb b/db/seeds.rb index a86380abf5..8f671dfc08 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -45,6 +45,13 @@ def open_file(file) BLANK_PARAMS = Array.new(8, { type: 'Blank' }) +STANDALONE_CLAIMS_FACT_CHECKS_PARAMS = (Array.new(8) do + { + description: Faker::Lorem.sentence, + context: Faker::Lorem.paragraph(sentence_count: 8) + } +end) + class Setup private @@ -323,7 +330,7 @@ def populate_projects def publish_fact_checks users.each_value do |user| - fact_checks = FactCheck.where(user: user).last(items_total/2) + fact_checks = user.claim_descriptions.where.not(project_media_id: nil).includes(:fact_check).map { |claim| claim.fact_check }.compact!.last(items_total/2) fact_checks[0, (fact_checks.size/2)].each { |fact_check| verify_fact_check_and_publish_report(fact_check.project_media)} end end @@ -419,6 +426,13 @@ def tipline_requests end end + def verified_standalone_claims_and_fact_checks + users.each_value do |user| + standalone_claims_and_fact_checks(user) + verify_standalone_claims_and_fact_checks(user) + end + end + private def medias_params @@ -707,6 +721,30 @@ def imported_fact_check_params(media_type) def channel(media_type) media_type == "Blank" ? { main: CheckChannels::ChannelCodes::FETCH } : { main: CheckChannels::ChannelCodes::MANUAL } end + + def standalone_claims_and_fact_checks(user) + STANDALONE_CLAIMS_FACT_CHECKS_PARAMS.each.with_index do |params, index| + claim_description_attributes = { + description: params[:description], + context: params[:context], + user: user, + team: user.teams[0], + fact_check_attributes: fact_check_params_for_half_the_claims(index, user), + } + + ClaimDescription.create!(claim_description_attributes) + end + end + + def verify_standalone_claims_and_fact_checks(user) + status = ['undetermined', 'not_applicable', 'in_progress', 'verified', 'false'] + + fact_checks = user.claim_descriptions.where(project_media_id: nil).includes(:fact_check).map { |claim| claim.fact_check }.compact! # some claims don't have fact checks, so they return nil + fact_checks.each do |fact_check| + fact_check.rating = status.sample + fact_check.save! + end + end end puts "If you want to create a new user: press enter" @@ -747,10 +785,12 @@ def channel(media_type) populated_workspaces.tipline_requests puts 'Publishing half of each user\'s Fact Checks...' populated_workspaces.publish_fact_checks - puts 'Creating Clusters' + puts 'Creating Clusters...' populated_workspaces.clusters(feed_2) - puts 'Creating Explainers' + puts 'Creating Explainers...' populated_workspaces.explainers + puts 'Creating Standalone Claims and FactChecks with different statuses...' + populated_workspaces.verified_standalone_claims_and_fact_checks rescue RuntimeError => e if e.message.include?('We could not parse this link') puts "—————" diff --git a/lib/relay.idl b/lib/relay.idl index 25e0255a5f..148f9925a3 100644 --- a/lib/relay.idl +++ b/lib/relay.idl @@ -516,7 +516,7 @@ type ApiKeyEdge { """ A union type of all article types we can handle """ -union ArticleUnion = Explainer +union ArticleUnion = Explainer | FactCheck """ The connection type for ArticleUnion. @@ -1179,7 +1179,7 @@ input CreateClaimDescriptionInput { clientMutationId: String context: String description: String - project_media_id: Int! + project_media_id: Int } """ @@ -2535,10 +2535,37 @@ input CreateExplainerInput { clientMutationId: String description: String language: String - title: String! + tags: [String] + title: String url: String } +""" +Autogenerated input type of CreateExplainerItem +""" +input CreateExplainerItemInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + explainerId: Int! + projectMediaId: Int! +} + +""" +Autogenerated return type of CreateExplainerItem +""" +type CreateExplainerItemPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + explainer: Explainer + explainer_item: ExplainerItem + explainer_itemEdge: ExplainerItemEdge + project_media: ProjectMedia +} + """ Autogenerated return type of CreateExplainer """ @@ -2563,7 +2590,9 @@ input CreateFactCheckInput { """ clientMutationId: String language: String + rating: String summary: String! + tags: [String] title: String! url: String } @@ -2580,6 +2609,7 @@ type CreateFactCheckPayload { clientMutationId: String fact_check: FactCheck fact_checkEdge: FactCheckEdge + team: Team } """ @@ -4125,6 +4155,30 @@ input DestroyExplainerInput { id: ID } +""" +Autogenerated input type of DestroyExplainerItem +""" +input DestroyExplainerItemInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + id: ID +} + +""" +Autogenerated return type of DestroyExplainerItem +""" +type DestroyExplainerItemPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + deletedId: ID + explainer: Explainer + project_media: ProjectMedia +} + """ Autogenerated return type of DestroyExplainer """ @@ -4159,6 +4213,7 @@ type DestroyFactCheckPayload { """ clientMutationId: String deletedId: ID + team: Team } """ @@ -8205,27 +8260,7 @@ type Explainer implements Node { id: ID! language: String permissions: String - tags( - """ - Returns the elements in the list that come after the specified cursor. - """ - after: String - - """ - Returns the elements in the list that come before the specified cursor. - """ - before: String - - """ - Returns the first _n_ elements from the list. - """ - first: Int - - """ - Returns the last _n_ elements from the list. - """ - last: Int - ): TagConnection + tags: [String] team: PublicTeam team_id: Int title: String @@ -8235,6 +8270,27 @@ type Explainer implements Node { user_id: Int } +""" +The connection type for Explainer. +""" +type ExplainerConnection { + """ + A list of edges. + """ + edges: [ExplainerEdge] + + """ + A list of nodes. + """ + nodes: [Explainer] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + totalCount: Int +} + """ An edge in a connection. """ @@ -8250,6 +8306,56 @@ type ExplainerEdge { node: Explainer } +""" +Explainer item type +""" +type ExplainerItem implements Node { + created_at: String + explainer: Explainer! + explainer_id: Int! + id: ID! + permissions: String + project_media: ProjectMedia! + project_media_id: Int! + updated_at: String +} + +""" +The connection type for ExplainerItem. +""" +type ExplainerItemConnection { + """ + A list of edges. + """ + edges: [ExplainerItemEdge] + + """ + A list of nodes. + """ + nodes: [ExplainerItem] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + totalCount: Int +} + +""" +An edge in a connection. +""" +type ExplainerItemEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: ExplainerItem +} + """ Autogenerated input type of ExtractText """ @@ -8280,9 +8386,13 @@ type FactCheck implements Node { created_at: String dbid: Int id: ID! + imported: Boolean language: String permissions: String + rating: String + report_status: String summary: String + tags: [String] title: String updated_at: String url: String @@ -9315,6 +9425,12 @@ type MutationType { """ input: CreateExplainerInput! ): CreateExplainerPayload + createExplainerItem( + """ + Parameters for CreateExplainerItem + """ + input: CreateExplainerItemInput! + ): CreateExplainerItemPayload createFactCheck( """ Parameters for CreateFactCheck @@ -9669,6 +9785,12 @@ type MutationType { """ input: DestroyExplainerInput! ): DestroyExplainerPayload + destroyExplainerItem( + """ + Parameters for DestroyExplainerItem + """ + input: DestroyExplainerItemInput! + ): DestroyExplainerItemPayload destroyFactCheck( """ Parameters for DestroyFactCheck @@ -10495,6 +10617,7 @@ type ProjectMedia implements Node { ): AnnotationUnionConnection annotations_count(annotation_type: String!): Int archived: Int + articles_count: Int assignments( """ Returns the elements in the list that come after the specified cursor. @@ -11365,6 +11488,50 @@ type ProjectMedia implements Node { """ last: Int ): DynamicConnection + explainer_items( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ExplainerItemConnection + explainers( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): ExplainerConnection + fact_check: FactCheck + fact_check_id: Int fact_check_published_on: Int feed_columns_values: JsonStringType field_value(annotation_type_field_name: String!): String @@ -11406,7 +11573,6 @@ type ProjectMedia implements Node { last_status: String last_status_obj: Dynamic linked_items_count: Int - list_columns_values: JsonStringType log( """ Returns the elements in the list that come after the specified cursor. @@ -12922,12 +13088,26 @@ type Team implements Node { Returns the first _n_ elements from the list. """ first: Int + imported: Boolean + language: [String] """ Returns the last _n_ elements from the list. """ last: Int + offset: Int = 0 + publisher_ids: [Int] + rating: [String] + report_status: [String] + sort: String = "title" + sort_type: String = "ASC" + standalone: Boolean + tags: [String] + text: String + updated_at: String + user_ids: [Int] ): ArticleUnionConnection + articles_count(article_type: String, imported: Boolean, language: [String], publisher_ids: [Int], rating: [String], report_status: [String], standalone: Boolean, tags: [String], text: String, updated_at: String, user_ids: [Int]): Int available_newsletter_header_types: JsonStringType avatar: String check_search_spam: CheckSearch @@ -13022,7 +13202,6 @@ type Team implements Node { """ last: Int ): TeamUserConnection - list_columns: JsonStringType medias_count: Int members_count: Int name: String! @@ -13842,6 +14021,7 @@ input UpdateClaimDescriptionInput { context: String description: String id: ID + project_media_id: Int } """ @@ -15344,7 +15524,8 @@ input UpdateExplainerInput { description: String id: ID language: String - title: String! + tags: [String] + title: String url: String } @@ -15371,7 +15552,9 @@ input UpdateFactCheckInput { clientMutationId: String id: ID language: String + rating: String summary: String + tags: [String] title: String url: String } @@ -15388,6 +15571,7 @@ type UpdateFactCheckPayload { clientMutationId: String fact_check: FactCheck fact_checkEdge: FactCheckEdge + team: Team } """ @@ -15846,7 +16030,6 @@ input UpdateTeamInput { language: String language_detection: Boolean languages: JsonStringType - list_columns: JsonStringType media_verification_statuses: JsonStringType name: String outgoing_urls_utm_code: String diff --git a/lib/tasks/migrate/20220703070839_add_language_to_fact_check.rake b/lib/tasks/migrate/20220703070839_add_language_to_fact_check.rake deleted file mode 100644 index bd6971e493..0000000000 --- a/lib/tasks/migrate/20220703070839_add_language_to_fact_check.rake +++ /dev/null @@ -1,26 +0,0 @@ -namespace :check do - namespace :migrate do - task add_language_to_fact_check: :environment do - started = Time.now.to_i - # Get latest team id - last_team_id = Rails.cache.read('check:migrate:add_language_to_fact_check:team_id') || 0 - Team.where('id > ?', last_team_id).find_each do |team| - puts "Processing team [#{team.slug}]" - language = team.default_language || 'en' - team.project_medias.select('fc.*') - .joins("INNER JOIN claim_descriptions cd ON project_medias.id = cd.project_media_id") - .joins("INNER JOIN fact_checks fc ON cd.id = fc.claim_description_id") - .find_in_batches(:batch_size => 2500) do |items| - ids = [] - items.each{ |i| ids << i['id'] } - puts "ids are :: #{ids.inspect}" - FactCheck.where(id: ids).update_all(language: language) - end - # log last team id - Rails.cache.write('check:migrate:add_language_to_fact_check:team_id', team.id) - end - minutes = ((Time.now.to_i - started) / 60).to_i - puts "[#{Time.now}] Done in #{minutes} minutes." - end - end -end \ No newline at end of file diff --git a/lib/tasks/migrate/20240703070839_add_language_to_fact_check.rake b/lib/tasks/migrate/20240703070839_add_language_to_fact_check.rake new file mode 100644 index 0000000000..289b7c9472 --- /dev/null +++ b/lib/tasks/migrate/20240703070839_add_language_to_fact_check.rake @@ -0,0 +1,68 @@ +namespace :check do + namespace :migrate do + task add_language_to_fact_check: :environment do + started = Time.now.to_i + # Get latest team id + last_team_id = Rails.cache.read('check:migrate:add_language_to_fact_check:team_id') || 0 + Team.where('id > ?', last_team_id).find_each do |team| + puts "Processing team [#{team.slug}]" + language = team.default_language || 'en' + team.project_medias.select('fc.*') + .joins("INNER JOIN claim_descriptions cd ON project_medias.id = cd.project_media_id") + .joins("INNER JOIN fact_checks fc ON cd.id = fc.claim_description_id") + .find_in_batches(:batch_size => 2500) do |items| + ids = [] + items.each{ |i| ids << i['id'] } + puts "ids are :: #{ids.inspect}" + FactCheck.where(id: ids).update_all(language: language) + end + # log last team id + Rails.cache.write('check:migrate:add_language_to_fact_check:team_id', team.id) + end + minutes = ((Time.now.to_i - started) / 60).to_i + puts "[#{Time.now}] Done in #{minutes} minutes." + end + + task add_report_information_to_fact_check: :environment do + started = Time.now.to_i + # Get latest team id + last_team_id = Rails.cache.read('check:migrate:add_report_information_to_fact_check:team_id') || 0 + Team.where('id > ?', last_team_id).find_each do |team| + puts "Processing team [#{team.slug}]" + team.project_medias.select('project_medias.id as id, fc.id as fc_id') + .joins("INNER JOIN claim_descriptions cd ON project_medias.id = cd.project_media_id") + .joins("INNER JOIN fact_checks fc ON cd.id = fc.claim_description_id") + .find_in_batches(:batch_size => 2500) do |items| + print '.' + pm_fc = {} + items.each{ |i| pm_fc[i['id']] = i['fc_id'] } + fc_fields = {} + # Collect report designer + Dynamic.where(annotation_type: 'report_design', annotated_type: 'ProjectMedia', annotated_id: pm_fc.keys).find_each do |rd| + print '.' + # Get report status and publisher id + state = rd.data['state'] + publisher_id = state == 'published' ? rd.annotator_id : nil + fc_fields[pm_fc[rd.annotated_id]] = { publisher_id: publisher_id, report_status: state } + end + # Add rating (depend on status cached field) + ProjectMedia.where(id: pm_fc.keys).find_each do |pm| + print '.' + tags = pm.tags_as_sentence.split(',') + fc_fields[pm_fc[pm.id]].merge!({ rating: pm.status, tags: tags }) + end + fc_items = [] + FactCheck.where(id: pm_fc.values).find_each do |fc| + fc_fields[fc.id].each { |field, value| fc.send("#{field}=", value) } + fc_items << fc.attributes + end + FactCheck.upsert_all(fc_items) + end + # log last team id + Rails.cache.write('check:migrate:add_report_information_to_fact_check:team_id', team.id) + end + minutes = ((Time.now.to_i - started) / 60).to_i + puts "[#{Time.now}] Done in #{minutes} minutes." + end + end +end \ No newline at end of file diff --git a/lib/tasks/migrate/20240713012502_set_imported_for_fact_checks.rake b/lib/tasks/migrate/20240713012502_set_imported_for_fact_checks.rake new file mode 100644 index 0000000000..23b85fc7eb --- /dev/null +++ b/lib/tasks/migrate/20240713012502_set_imported_for_fact_checks.rake @@ -0,0 +1,20 @@ +namespace :check do + namespace :migrate do + task set_imported_for_fact_checks: :environment do + puts "[#{Time.now}] Setting imported field for existing fact-checks" + started = Time.now.to_i + BATCH_SIZE = 1000 + query = FactCheck.joins(:user).where('users.type' => 'BotUser').where(imported: false) + count = query.count + total = 0 + while count > 0 + puts "[#{Time.now}] Updating maximum #{BATCH_SIZE} fact-checks, out of #{count}" + query.limit(BATCH_SIZE).update_all(imported: true) + total += (BATCH_SIZE < count ? BATCH_SIZE : count) + count = query.count + end + minutes = ((Time.now.to_i - started) / 60).to_i + puts "[#{Time.now}] Done in #{minutes} minutes. Updated #{total} fact-checks." + end + end +end diff --git a/lib/tasks/migrate/20240715013839_add_team_id_to_claim_description.rake b/lib/tasks/migrate/20240715013839_add_team_id_to_claim_description.rake new file mode 100644 index 0000000000..35fc4517e2 --- /dev/null +++ b/lib/tasks/migrate/20240715013839_add_team_id_to_claim_description.rake @@ -0,0 +1,26 @@ +namespace :check do + namespace :migrate do + task add_team_id_to_claim_description: :environment do |_t, args| + started = Time.now.to_i + slugs = args.extras + condition = {} + if slugs.blank? + last_team_id = Rails.cache.read('check:migrate:add_team_id_to_claim_description:team_id') || 0 + else + last_team_id = 0 + condition = { slug: slugs } + end + Team.where(condition).where('id > ?', last_team_id).find_each do |team| + puts "Processing team [#{team.slug}]" + team.project_medias.joins(:claim_description).find_in_batches(batch_size: 2500) do |pms| + print '.' + ids = pms.map(&:id) + ClaimDescription.where(project_media_id: ids).update_all(team_id: team.id) + end + Rails.cache.write('check:migrate:add_team_id_to_claim_description:team_id', team.id) if slugs.blank? + end + minutes = ((Time.now.to_i - started) / 60).to_i + puts "[#{Time.now}] Done in #{minutes} minutes." + end + end +end \ No newline at end of file diff --git a/public/relay.json b/public/relay.json index 458c1b5863..99542ca4c9 100644 --- a/public/relay.json +++ b/public/relay.json @@ -2394,6 +2394,11 @@ "kind": "OBJECT", "name": "Explainer", "ofType": null + }, + { + "kind": "OBJECT", + "name": "FactCheck", + "ofType": null } ] }, @@ -5880,13 +5885,9 @@ "name": "project_media_id", "description": null, "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } + "kind": "SCALAR", + "name": "Int", + "ofType": null }, "defaultValue": null, "isDeprecated": false, @@ -15206,13 +15207,9 @@ "name": "title", "description": null, "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } + "kind": "SCALAR", + "name": "String", + "ofType": null }, "defaultValue": null, "isDeprecated": false, @@ -15254,6 +15251,77 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "tags", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "CreateExplainerItemInput", + "description": "Autogenerated input type of CreateExplainerItem", + "fields": null, + "inputFields": [ + { + "name": "explainerId", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "projectMediaId", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "clientMutationId", "description": "A unique identifier for the client performing the mutation.", @@ -15271,6 +15339,89 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "CreateExplainerItemPayload", + "description": "Autogenerated return type of CreateExplainerItem", + "fields": [ + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "explainer", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "Explainer", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "explainer_item", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "ExplainerItem", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "explainer_itemEdge", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "ExplainerItemEdge", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "project_media", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "ProjectMedia", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "CreateExplainerPayload", @@ -15370,6 +15521,34 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "tags", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "rating", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "title", "description": null, @@ -15495,6 +15674,20 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "team", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "Team", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -23520,65 +23713,169 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "OBJECT", - "name": "DestroyExplainerPayload", - "description": "Autogenerated return type of DestroyExplainer", - "fields": [ - { - "name": "clientMutationId", - "description": "A unique identifier for the client performing the mutation.", - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "deletedId", - "description": null, - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "team", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "Team", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [ - - ], - "enumValues": null, - "possibleTypes": null - }, { "kind": "INPUT_OBJECT", - "name": "DestroyFactCheckInput", - "description": "Autogenerated input type of DestroyFactCheck", + "name": "DestroyExplainerItemInput", + "description": "Autogenerated input type of DestroyExplainerItem", + "fields": null, + "inputFields": [ + { + "name": "id", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "DestroyExplainerItemPayload", + "description": "Autogenerated return type of DestroyExplainerItem", + "fields": [ + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deletedId", + "description": null, + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "explainer", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "Explainer", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "project_media", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "ProjectMedia", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "DestroyExplainerPayload", + "description": "Autogenerated return type of DestroyExplainer", + "fields": [ + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deletedId", + "description": null, + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "team", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "Team", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "DestroyFactCheckInput", + "description": "Autogenerated input type of DestroyFactCheck", "fields": null, "inputFields": [ { @@ -23656,6 +23953,20 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "team", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "Team", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -44414,59 +44725,16 @@ "name": "tags", "description": null, "args": [ - { - "name": "after", - "description": "Returns the elements in the list that come after the specified cursor.", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "before", - "description": "Returns the elements in the list that come before the specified cursor.", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "first", - "description": "Returns the first _n_ elements from the list.", - "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "last", - "description": "Returns the last _n_ elements from the list.", - "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - } + ], "type": { - "kind": "OBJECT", - "name": "TagConnection", - "ofType": null + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } }, "isDeprecated": false, "deprecationReason": null @@ -44581,6 +44849,87 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "ExplainerConnection", + "description": "The connection type for Explainer.", + "fields": [ + { + "name": "edges", + "description": "A list of edges.", + "args": [ + + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ExplainerEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "nodes", + "description": "A list of nodes.", + "args": [ + + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Explainer", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": null, + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "ExplainerEdge", @@ -44626,6 +44975,281 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "ExplainerItem", + "description": "Explainer item type", + "fields": [ + { + "name": "created_at", + "description": null, + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "explainer", + "description": null, + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Explainer", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "explainer_id", + "description": null, + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": null, + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "permissions", + "description": null, + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "project_media", + "description": null, + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ProjectMedia", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "project_media_id", + "description": null, + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "updated_at", + "description": null, + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Node", + "ofType": null + } + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ExplainerItemConnection", + "description": "The connection type for ExplainerItem.", + "fields": [ + { + "name": "edges", + "description": "A list of edges.", + "args": [ + + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ExplainerItemEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "nodes", + "description": "A list of nodes.", + "args": [ + + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "ExplainerItem", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": null, + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "ExplainerItemEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "cursor", + "description": "A cursor for use in pagination.", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "node", + "description": "The item at the end of the edge.", + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "ExplainerItem", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "ExtractTextInput", @@ -44771,6 +45395,20 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "imported", + "description": null, + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "language", "description": null, @@ -44799,6 +45437,34 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "rating", + "description": null, + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "report_status", + "description": null, + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "summary", "description": null, @@ -44813,6 +45479,24 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "tags", + "description": null, + "args": [ + + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "title", "description": null, @@ -50522,6 +51206,35 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "createExplainerItem", + "description": null, + "args": [ + { + "name": "input", + "description": "Parameters for CreateExplainerItem", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "CreateExplainerItemInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "CreateExplainerItemPayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "createFactCheck", "description": null, @@ -52233,6 +52946,35 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "destroyExplainerItem", + "description": null, + "args": [ + { + "name": "input", + "description": "Parameters for DestroyExplainerItem", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "DestroyExplainerItemInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "DestroyExplainerItemPayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "destroyFactCheck", "description": null, @@ -55337,6 +56079,11 @@ "name": "Explainer", "ofType": null }, + { + "kind": "OBJECT", + "name": "ExplainerItem", + "ofType": null + }, { "kind": "OBJECT", "name": "FactCheck", @@ -56635,6 +57382,20 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "articles_count", + "description": null, + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "assignments", "description": null, @@ -59732,6 +60493,156 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "explainer_items", + "description": null, + "args": [ + { + "name": "after", + "description": "Returns the elements in the list that come after the specified cursor.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "before", + "description": "Returns the elements in the list that come before the specified cursor.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "first", + "description": "Returns the first _n_ elements from the list.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "last", + "description": "Returns the last _n_ elements from the list.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "ExplainerItemConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "explainers", + "description": null, + "args": [ + { + "name": "after", + "description": "Returns the elements in the list that come after the specified cursor.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "before", + "description": "Returns the elements in the list that come before the specified cursor.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "first", + "description": "Returns the first _n_ elements from the list.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "last", + "description": "Returns the last _n_ elements from the list.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "ExplainerConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fact_check", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "FactCheck", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fact_check_id", + "description": null, + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "fact_check_published_on", "description": null, @@ -60103,20 +61014,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "list_columns_values", - "description": null, - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "JsonStringType", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "log", "description": null, @@ -67712,6 +68609,186 @@ "defaultValue": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "offset", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": "0", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "sort", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": "\"title\"", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "sort_type", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": "\"ASC\"", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "user_ids", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "tags", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "language", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "updated_at", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "text", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "standalone", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "publisher_ids", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "report_status", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "rating", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "imported", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ], "type": { @@ -67722,6 +68799,175 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "articles_count", + "description": null, + "args": [ + { + "name": "article_type", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "user_ids", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "tags", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "language", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "updated_at", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "text", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "standalone", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "publisher_ids", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "report_status", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "rating", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "imported", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "available_newsletter_header_types", "description": null, @@ -68362,20 +69608,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "list_columns", - "description": null, - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "JsonStringType", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "medias_count", "description": null, @@ -72853,6 +74085,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "project_media_id", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "clientMutationId", "description": "A unique identifier for the client performing the mutation.", @@ -83620,8 +84864,246 @@ }, { "kind": "OBJECT", - "name": "UpdateDynamicPayload", - "description": "Autogenerated return type of UpdateDynamic", + "name": "UpdateDynamicPayload", + "description": "Autogenerated return type of UpdateDynamic", + "fields": [ + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "dynamic", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "Dynamic", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "dynamicEdge", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "DynamicEdge", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "project", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "Project", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "project_media", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "ProjectMedia", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "source", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "Source", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "task", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "Task", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "version", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "Version", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "versionEdge", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "VersionEdge", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "UpdateExplainerInput", + "description": "Autogenerated input type of UpdateExplainer", + "fields": null, + "inputFields": [ + { + "name": "id", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "title", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "url", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "language", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "tags", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "UpdateExplainerPayload", + "description": "Autogenerated return type of UpdateExplainer", "fields": [ { "name": "clientMutationId", @@ -83638,112 +85120,42 @@ "deprecationReason": null }, { - "name": "dynamic", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "Dynamic", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "dynamicEdge", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "DynamicEdge", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "project", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "Project", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "project_media", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "ProjectMedia", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "source", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "Source", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "task", + "name": "explainer", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Task", + "name": "Explainer", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "version", + "name": "explainerEdge", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Version", + "name": "ExplainerEdge", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "versionEdge", + "name": "team", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "VersionEdge", + "name": "Team", "ofType": null }, "isDeprecated": false, @@ -83759,8 +85171,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "UpdateExplainerInput", - "description": "Autogenerated input type of UpdateExplainer", + "name": "UpdateFactCheckInput", + "description": "Autogenerated input type of UpdateFactCheck", "fields": null, "inputFields": [ { @@ -83775,34 +85187,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "title", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "description", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "url", "description": null, @@ -83828,123 +85212,23 @@ "deprecationReason": null }, { - "name": "clientMutationId", - "description": "A unique identifier for the client performing the mutation.", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "UpdateExplainerPayload", - "description": "Autogenerated return type of UpdateExplainer", - "fields": [ - { - "name": "clientMutationId", - "description": "A unique identifier for the client performing the mutation.", - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "explainer", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "Explainer", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "explainerEdge", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "ExplainerEdge", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "team", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "Team", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [ - - ], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "UpdateFactCheckInput", - "description": "Autogenerated input type of UpdateFactCheck", - "fields": null, - "inputFields": [ - { - "name": "id", - "description": null, - "type": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "url", + "name": "tags", "description": null, "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "LIST", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } }, "defaultValue": null, "isDeprecated": false, "deprecationReason": null }, { - "name": "language", + "name": "rating", "description": null, "type": { "kind": "SCALAR", @@ -84056,6 +85340,20 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "team", + "description": null, + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "Team", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -86983,18 +88281,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "list_columns", - "description": null, - "type": { - "kind": "SCALAR", - "name": "JsonStringType", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "tipline_inbox_filters", "description": null, diff --git a/test/controllers/graphql_controller_12_test.rb b/test/controllers/graphql_controller_12_test.rb index d80ac91d9f..6349054ba9 100644 --- a/test/controllers/graphql_controller_12_test.rb +++ b/test/controllers/graphql_controller_12_test.rb @@ -332,7 +332,7 @@ def teardown create_cluster_project_media cluster: c, project_media: pm authenticate_with_user(@u) - query = 'query { feed(id: "' + f.id.to_s + '") { cluster(project_media_id: ' + pm.id.to_s + ') { dbid, project_media(id: ' + pm.id.to_s + ') { id, imported_from_feed { id } }, project_medias(teamId: ' + @t.id.to_s + ', first: 1) { edges { node { id } } }, cluster_teams(first: 10) { edges { node { id, team { name }, last_request_date, media_count, requests_count, fact_checks(first: 1) { edges { node { id } } } } } } } } }' + query = 'query { feed(id: "' + f.id.to_s + '") { cluster(project_media_id: ' + pm.id.to_s + ') { dbid, project_media(id: ' + pm.id.to_s + ') { id, articles_count, imported_from_feed { id } }, project_medias(teamId: ' + @t.id.to_s + ', first: 1) { edges { node { id } } }, cluster_teams(first: 10) { edges { node { id, team { name }, last_request_date, media_count, requests_count, fact_checks(first: 1) { edges { node { id } } } } } } } } }' post :create, params: { query: query } assert_response :success assert_equal c.id, JSON.parse(@response.body)['data']['feed']['cluster']['dbid'] @@ -375,21 +375,50 @@ def teardown assert_equal 3, @t.reload.project_medias.count end - test "should get team articles" do + test "should get team articles (explainers)" do + Sidekiq::Testing.fake! @t.set_explainers_enabled = true @t.save! ex = create_explainer team: @t tag = create_tag annotated: ex authenticate_with_user(@u) - query = "query { team(slug: \"#{@t.slug}\") { get_explainers_enabled, articles(article_type: \"explainer\") { edges { node { ... on Explainer { dbid, tags { edges { node { dbid } } } } } } } } }" + query = "query { team(slug: \"#{@t.slug}\") { get_explainers_enabled, articles_count(article_type: \"explainer\"), articles(article_type: \"explainer\") { edges { node { ... on Explainer { dbid, tags } } } } } }" post :create, params: { query: query, team: @t.slug } + assert_response :success team = JSON.parse(@response.body)['data']['team'] + assert_equal 1, team['articles_count'] assert team['get_explainers_enabled'] data = team['articles']['edges'] assert_equal [ex.id], data.collect{ |edge| edge['node']['dbid'] } - tags = data[0]['node']['tags']['edges'] - assert_equal [tag.id.to_s], tags.collect{ |edge| edge['node']['dbid'] } + end + + test "should get team articles (fact-checks)" do + Sidekiq::Testing.fake! + authenticate_with_user(@u) + pm = create_project_media team: @t + cd = create_claim_description project_media: pm + fc = create_fact_check claim_description: cd, tags: ['foo', 'bar'] + query = "query { team(slug: \"#{@t.slug}\") { articles_count(article_type: \"fact-check\"), articles(article_type: \"fact-check\") { edges { node { ... on FactCheck { dbid, tags } } } } } }" + post :create, params: { query: query, team: @t.slug } + assert_response :success + team = JSON.parse(@response.body)['data']['team'] + assert_equal 1, team['articles_count'] + data = team['articles']['edges'] + assert_equal [fc.id], data.collect{ |edge| edge['node']['dbid'] } + end + + test "should get team articles (all)" do + Sidekiq::Testing.fake! + authenticate_with_user(@u) + pm = create_project_media team: @t + cd = create_claim_description project_media: pm + create_fact_check claim_description: cd, tags: ['foo', 'bar'] + create_explainer team: @t + query = "query { team(slug: \"#{@t.slug}\") { articles_count } }" + post :create, params: { query: query, team: @t.slug } assert_response :success + team = JSON.parse(@response.body)['data']['team'] + assert_equal 2, team['articles_count'] end test "should create api key" do diff --git a/test/models/explainer_item_test.rb b/test/models/explainer_item_test.rb new file mode 100644 index 0000000000..477a2b730a --- /dev/null +++ b/test/models/explainer_item_test.rb @@ -0,0 +1,92 @@ +require_relative '../test_helper' + +class ExplainerItemTest < ActiveSupport::TestCase + def setup + @t = create_team + @pm = create_project_media(team: @t, media: create_claim_media(quote: 'Test')) + @ex = create_explainer(team: @t) + end + + def teardown + end + + test "should create explainer item" do + assert_difference 'ExplainerItem.count' do + ExplainerItem.create! explainer: @ex, project_media: @pm + end + end + + test "should be associated with explainers" do + assert_difference 'ExplainerItem.count' do + @ex.project_medias << @pm + end + assert_equal 1, @ex.project_medias.count + end + + test "should be associated with items" do + assert_difference 'ExplainerItem.count' do + @pm.explainers << @ex + end + assert_equal 1, @pm.explainers.count + end + + test "should not create explainer item without mandatory fields" do + ei = ExplainerItem.new + assert_not ei.valid? + ei = ExplainerItem.new project_media: @pm + assert_not ei.valid? + ei = ExplainerItem.new explainer: @ex + assert_not ei.valid? + ei = ExplainerItem.new project_media: @pm, explainer: @ex + assert ei.valid? + end + + test "should not create associate explainer and item from different workspaces" do + t1 = create_team + e1 = create_explainer team: t1 + pm1 = create_project_media team: t1 + t2 = create_team + e2 = create_explainer team: t2 + pm2 = create_project_media team: t2 + assert ExplainerItem.new(project_media: pm1, explainer: e1).valid? + assert ExplainerItem.new(project_media: pm2, explainer: e2).valid? + assert_not ExplainerItem.new(project_media: pm1, explainer: e2).valid? + assert_not ExplainerItem.new(project_media: pm2, explainer: e1).valid? + end + + test "should have permission to create explainer item" do + t1 = create_team + u1 = create_user + create_team_user user: u1, team: t1 + e1 = create_explainer team: t1 + pm1 = create_project_media team: t1 + + t2 = create_team + u2 = create_user + create_team_user user: u2, team: t2 + e2 = create_explainer team: t2 + pm2 = create_project_media team: t2 + + with_current_user_and_team u1, t1 do + assert_difference 'ExplainerItem.count' do + pm1.explainers << e1 + end + assert_no_difference 'ExplainerItem.count' do + assert_raises RuntimeError do # Permission error + pm2.explainers << e2 + end + end + end + + with_current_user_and_team u2, t2 do + assert_no_difference 'ExplainerItem.count' do + assert_raises RuntimeError do # Permission error + pm1.explainers << e1 + end + end + assert_difference 'ExplainerItem.count' do + pm2.explainers << e2 + end + end + end +end diff --git a/test/models/explainer_test.rb b/test/models/explainer_test.rb index bae8ccbf47..d2cd7d6a2d 100644 --- a/test/models/explainer_test.rb +++ b/test/models/explainer_test.rb @@ -91,9 +91,17 @@ def setup end end - test "should tag explainer" do + test "should tag explainer using annotation" do ex = create_explainer tag = create_tag annotated: ex assert_equal [tag], ex.annotations('tag') end + + test "should create tag texts when setting tags" do + Sidekiq::Testing.inline! do + assert_difference 'TagText.count' do + create_explainer tags: ['foo'] + end + end + end end diff --git a/test/models/fact_check_test.rb b/test/models/fact_check_test.rb index b915afa435..670cd0e599 100644 --- a/test/models/fact_check_test.rb +++ b/test/models/fact_check_test.rb @@ -294,6 +294,41 @@ def setup assert_not_empty fc.reload.title end + test "should validate rating" do + assert_no_difference 'FactCheck.count' do + assert_raises ActiveRecord::RecordInvalid do + create_fact_check rating: 'invalid_status' + end + end + assert_difference 'FactCheck.count' do + create_fact_check rating: 'verified' + end + # Validate custom status + t = create_team + value = { + label: 'Status', + default: 'stop', + active: 'done', + statuses: [ + { id: 'stop', label: 'Stopped', completed: '', description: 'Not started yet', style: { backgroundColor: '#a00' } }, + { id: 'done', label: 'Done!', completed: '', description: 'Nothing left to be done here', style: { backgroundColor: '#fc3' } } + ] + } + t.send :set_media_verification_statuses, value + t.save! + pm = create_project_media team: t + cd = create_claim_description project_media: pm + assert_no_difference 'FactCheck.count' do + assert_raises ActiveRecord::RecordInvalid do + create_fact_check claim_description: cd, rating: 'invalid_status' + end + end + allowed_statuses = t.reload.verification_statuses('media', nil)['statuses'].collect{|s| s[:id]} + assert_difference 'FactCheck.count' do + create_fact_check claim_description: cd, rating: 'stop' + end + end + test "should create many fact-checks without signature" do assert_difference 'FactCheck.count', 2 do create_fact_check signature: nil @@ -373,4 +408,144 @@ def setup assert_equal 'published', pm.reload.report_status end end + + test "should index report information in fact check" do + create_verification_status_stuff + t = create_team + u = create_user + create_team_user team: t, user: u, role: 'admin' + RequestStore.store[:skip_cached_field_update] = false + Sidekiq::Testing.inline! do + with_current_user_and_team(u, t) do + pm = create_project_media team: t + cd = create_claim_description project_media: pm + s = pm.last_verification_status_obj + s.status = 'verified' + s.save! + r = publish_report(pm) + fc = cd.fact_check + fc.title = 'Foo Bar' + fc.save! + fc = fc.reload + assert_equal u.id, fc.publisher_id + assert_equal 'published', fc.report_status + assert_equal 'verified', fc.rating + # Verify fact-checks filter + filters = { publisher_ids: [u.id] } + assert_equal [fc.id], t.filtered_fact_checks(filters).map(&:id) + filters = { rating: ['verified'] } + assert_equal [fc.id], t.filtered_fact_checks(filters).map(&:id) + filters = { report_status: ['published'] } + assert_equal [fc.id], t.filtered_fact_checks(filters).map(&:id) + filters = { publisher_ids: [u.id], rating: ['verified'], report_status: ['published'] } + assert_equal [fc.id], t.filtered_fact_checks(filters).map(&:id) + r = Dynamic.find(r.id) + r.set_fields = { state: 'paused' }.to_json + r.action = 'pause' + r.save! + fc = fc.reload + assert_nil fc.publisher_id + assert_equal 'paused', fc.report_status + assert_equal 'verified', fc.rating + s.status = 'in_progress' + s.save! + assert_equal 'in_progress', fc.reload.rating + # Verify fact-checks filter + filters = { publisher_ids: [u.id] } + assert_empty t.filtered_fact_checks(filters).map(&:id) + filters = { rating: ['verified'] } + assert_empty t.filtered_fact_checks(filters).map(&:id) + filters = { report_status: ['published'] } + assert_empty t.filtered_fact_checks(filters).map(&:id) + filters = { rating: ['in_progress'], report_status: ['paused'] } + assert_equal [fc.id], t.filtered_fact_checks(filters).map(&:id) + # Verify text filter + filters = { text: 'Test' } + assert_empty t.filtered_fact_checks(filters).map(&:id) + filters = { text: 'Foo' } + assert_equal [fc.id], t.filtered_fact_checks(filters).map(&:id) + # Update item status based on factcheck rating + fc.rating = 'verified' + fc.save! + s = pm.reload.last_verification_status_obj + assert_equal 'verified', s.status + end + end + end + + test "should set fact-check as imported" do + assert !create_fact_check(user: create_user).imported + assert create_fact_check(user: create_bot_user).imported + end + + test "should set initial rating" do + create_verification_status_stuff + + # Test core statuses first + t = create_team + pm = create_project_media team: t + cd = create_claim_description project_media: pm + fc = create_fact_check claim_description: cd + assert_equal 'undetermined', fc.reload.rating + fc.rating = 'in_progress' + fc.save! + assert_equal 'in_progress', pm.reload.last_status + + # Test custom statuses now + t = create_team + value = { + "label": "Custom Status Label", + "active": "in_progress", + "default": "new", + "statuses": [ + { + "id": "new", + "style": { + "color": "blue" + }, + "locales": { + "en": { + "label": "New", + "description": "An item that did not start yet" + }, + "pt": { + "label": "Novo", + "description": "Um item que ainda não começou a ser verificado" + } + } + }, + { + "id": "in_progress", + "style": { + "color": "yellow" + }, + "locales": { + "en": { + "label": "Working on it", + "description": "We are working on it" + }, + "pt": { + "label": "Estamos trabalhando nisso", + "description": "Estamos trabalhando nisso" + } + } + } + ] + } + t.set_media_verification_statuses(value) + t.save! + + pm = create_project_media team: t + cd = create_claim_description project_media: pm + fc = create_fact_check claim_description: cd + assert_equal 'new', fc.reload.rating + fc.rating = 'in_progress' + fc.save! + assert_equal 'in_progress', pm.reload.last_status + end + + test "should have team" do + fc = create_fact_check + assert_not_nil fc.team + end end diff --git a/test/models/project_media_6_test.rb b/test/models/project_media_6_test.rb index d4380406c5..921e03dcfa 100644 --- a/test/models/project_media_6_test.rb +++ b/test/models/project_media_6_test.rb @@ -484,4 +484,13 @@ def setup pms = ProjectMedia.where(team: t).to_a assert_queries(1, '=') { pms.map(&:team_avatar) } end + + test "should return fact-check" do + pm = create_project_media + assert_nil pm.fact_check + cd = create_claim_description project_media: pm + assert_nil pm.fact_check + fc = create_fact_check claim_description: cd + assert_equal fc, pm.fact_check + end end diff --git a/test/models/team_test.rb b/test/models/team_test.rb index f480761155..fabbc09b52 100644 --- a/test/models/team_test.rb +++ b/test/models/team_test.rb @@ -1219,7 +1219,7 @@ def setup t.save! u = create_user create_team_user team: t, user: u, role: 'admin' - with_current_user_and_team(u, t) do + with_current_user_and_team(u, t) do pm = create_project_media team: t, disable_es_callbacks: false cd = create_claim_description project_media: pm, disable_es_callbacks: false fc = create_fact_check claim_description: cd, language: 'fr' @@ -1258,4 +1258,9 @@ def setup assert_equal ['en'], result['fact_check_languages'] end end + + test "should return no team fact-checks by default" do + t = create_team + assert_equal [], t.fact_checks.to_a + end end