From 5e268211b530b7bb8a429e10807f770ef9a0c0bc Mon Sep 17 00:00:00 2001 From: Caio Almeida <117518+caiosba@users.noreply.github.com> Date: Thu, 25 Jan 2024 07:48:44 -0300 Subject: [PATCH 1/4] React to the Smooch trigger related to story mention. (#1781) The tipline bot should react to the Smooch trigger that happens when a connected Instagram account is mentioned in a story. This is required in order for the Facebook app to be approved. Fixes: CV2-3727. --- app/models/bot/smooch.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/bot/smooch.rb b/app/models/bot/smooch.rb index d346671144..2ba68ffd6b 100644 --- a/app/models/bot/smooch.rb +++ b/app/models/bot/smooch.rb @@ -302,7 +302,7 @@ def self.run(body) when 'message:delivery:failure' self.resend_message(json) true - when 'conversation:start' + when 'conversation:start', 'conversation:referral' message = { '_id': json['conversation']['_id'], authorId: json['appUser']['_id'], From 5a16fd69f5872e61c691596ca062344aaed1fda9 Mon Sep 17 00:00:00 2001 From: Jay Joshua <7008757+jayjay-w@users.noreply.github.com> Date: Thu, 25 Jan 2024 13:00:38 +0100 Subject: [PATCH 2/4] Fix failing test regarding unlocking user accounts (#1782) There is a failing test in SessionsController where travel_to is behaving differently in CI vs dev. --- test/controllers/sessions_controller_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb index 6c34c3e6a7..ce63e1c7c0 100644 --- a/test/controllers/sessions_controller_test.rb +++ b/test/controllers/sessions_controller_test.rb @@ -94,7 +94,7 @@ def setup post :create, params: { api_user: { email: 'test@test.com', password: '12345679' } } end - travel_to CheckConfig.get('devise_unlock_accounts_after', 5, :integer) + 1.hours.from_now do + travel_to CheckConfig.get('devise_unlock_accounts_after', 5, :integer).hours.from_now + 1.hour do u.reload assert !u.access_locked? assert_nil u.locked_at From c00bb0bebbc682f227b27e71a5fb2796a9e61180 Mon Sep 17 00:00:00 2001 From: Caio Almeida <117518+caiosba@users.noreply.github.com> Date: Sat, 27 Jan 2024 10:23:18 -0300 Subject: [PATCH 3/4] NLU Rate Limits (#1783) **Description** As part of the ongoing development of the Natural Language Understanding (NLU) feature, it has been identified that the current implementation of the text similarity service lacks robust handling of concurrent requests. The absence of efficient concurrency management may lead to degraded performance and potential service disruptions when multiple requests are made simultaneously. **Issue** The primary issue is the lack of a concurrency control mechanism, resulting in potential race conditions and suboptimal response times during peak usage periods. It is imperative to enhance the system's capability to handle concurrent requests seamlessly to ensure a smooth and responsive user experience. **Objective** The goal of this ticket is to implement rate limits as a concurrent request handling mechanism for the NLU feature. **Changes** - Store messages even before they are parsed - Keep a global NLU counter and increment/decrement it in a thread-safe way as NLU operations start and finish - Global rate limit: do not process using NLU when the current number of NLU operations is greater than the limit defined in a configuration key - User rate limit: do not process using NLU when the user who sent the message has sent more messages in the last seconds than the limit defined in a configuration key --- app/lib/smooch_nlu.rb | 73 +++++++++++++++------ app/lib/smooch_nlu_menus.rb | 4 +- app/models/bot/smooch.rb | 6 +- app/models/concerns/tipline_resource_nlu.rb | 4 +- config/config.yml.example | 6 +- test/lib/smooch_nlu_test.rb | 29 +++++++- test/models/bot/smooch_8_test.rb | 1 + test/models/bot_user_2_test.rb | 8 +++ 8 files changed, 101 insertions(+), 30 deletions(-) diff --git a/app/lib/smooch_nlu.rb b/app/lib/smooch_nlu.rb index 2af050bc4d..01e15fb32f 100644 --- a/app/lib/smooch_nlu.rb +++ b/app/lib/smooch_nlu.rb @@ -1,6 +1,6 @@ class SmoochNlu - class SmoochBotNotInstalledError < ::ArgumentError - end + class SmoochBotNotInstalledError < ::ArgumentError; end + class SmoochNluError < ::StandardError; end # FIXME: Make it more flexible # FIXME: Once we support paraphrase-multilingual-mpnet-base-v2 make it the only model used @@ -10,6 +10,8 @@ class SmoochBotNotInstalledError < ::ArgumentError Bot::Alegre::PARAPHRASE_MULTILINGUAL_MODEL => 0.6 } + NLU_GLOBAL_COUNTER_KEY = 'nlu_global_counter' + include SmoochNluMenus def initialize(team_slug) @@ -59,13 +61,23 @@ def self.disambiguation_threshold CheckConfig.get('nlu_disambiguation_threshold', 0.11, :float).to_f end - def self.alegre_matches_from_message(message, language, context, alegre_result_key) + def self.alegre_matches_from_message(message, language, context, alegre_result_key, uid) # FIXME: Raise exception if not in a tipline context (so, if Bot::Smooch.config is nil) matches = [] - team_slug = Team.find(Bot::Smooch.config['team_id']).slug - params = nil - response = nil - if Bot::Smooch.config.to_h['nlu_enabled'] + unless Bot::Smooch.config.to_h['nlu_enabled'] + return [] + end + if self.nlu_global_rate_limit_reached? + CheckSentry.notify(SmoochNluError.new('NLU global rate limit reached.')) + return [] + end + if self.nlu_user_rate_limit_reached?(uid) + CheckSentry.notify(SmoochNluError.new('NLU user rate limit reached.'), user_id: uid) + return [] + end + begin + self.increment_global_counter + team_slug = Team.find(Bot::Smooch.config['team_id']).slug # FIXME: In the future we could consider matches across all languages when options is nil # FIXME: No need to call Alegre if it's an exact match to one of the keywords # FIXME: No need to call Alegre if message has no word characters @@ -94,22 +106,45 @@ def self.alegre_matches_from_message(message, language, context, alegre_result_k ranked_options = sorted_options.map{ |o| { 'key' => o.dig('_source', 'context', alegre_result_key), 'score' => o['_score'] } } matches = ranked_options - # FIXME: Deal with ties (i.e., where two options have an equal _score or count) + # In all cases log for analysis + log = { + version: '0.1', # Update if schema changes + datetime: DateTime.current, + team_slug: team_slug, + user_query: message, + alegre_query: params, + alegre_response: response, + matches: matches + } + Rails.logger.info("[Smooch NLU] [Matches From Message] #{log.to_json}") + rescue StandardError => e + CheckSentry.notify(SmoochNluError.new("NLU exception: #{e.message}"), exception: e) + matches = [] + ensure + self.decrement_global_counter end - # In all cases log for analysis - log = { - version: "0.1", # Update if schema changes - datetime: DateTime.current, - team_slug: team_slug, - user_query: message, - alegre_query: params, - alegre_response: response, - matches: matches - } - Rails.logger.info("[Smooch NLU] [Matches From Message] #{log.to_json}") matches end + def self.nlu_global_rate_limit_reached? + redis = Redis.new(REDIS_CONFIG) + redis.get(NLU_GLOBAL_COUNTER_KEY).to_i > CheckConfig.get('nlu_global_rate_limit', 100, :integer) + end + + def self.nlu_user_rate_limit_reached?(uid) + TiplineMessage.where(uid: uid, created_at: Time.now.ago(1.minute)..Time.now, state: 'received').count > CheckConfig.get('nlu_user_rate_limit', 30, :integer) + end + + def self.increment_global_counter + redis = Redis.new(REDIS_CONFIG) + redis.incr(NLU_GLOBAL_COUNTER_KEY) + end + + def self.decrement_global_counter + redis = Redis.new(REDIS_CONFIG) + redis.decr(NLU_GLOBAL_COUNTER_KEY) + end + private def toggle!(enabled) diff --git a/app/lib/smooch_nlu_menus.rb b/app/lib/smooch_nlu_menus.rb index b1c14e26d0..9c602c0edf 100644 --- a/app/lib/smooch_nlu_menus.rb +++ b/app/lib/smooch_nlu_menus.rb @@ -67,13 +67,13 @@ def update_menu_option_keywords(language, menu, menu_option_index, keyword, oper end module ClassMethods - def menu_options_from_message(message, language, options) + def menu_options_from_message(message, language, options, uid) return [{ 'smooch_menu_option_value' => 'main_state' }] if message == 'cancel_nlu' return [] if options.blank? context = { context: ALEGRE_CONTEXT_KEY_MENU } - matches = SmoochNlu.alegre_matches_from_message(message, language, context, 'menu_option_id') + matches = SmoochNlu.alegre_matches_from_message(message, language, context, 'menu_option_id', uid) # Select the top two menu options that exists in `options` top_options = [] matches.each do |r| diff --git a/app/models/bot/smooch.rb b/app/models/bot/smooch.rb index 2ba68ffd6b..7f0ac223ae 100644 --- a/app/models/bot/smooch.rb +++ b/app/models/bot/smooch.rb @@ -295,8 +295,8 @@ def self.run(body) 'capi:verification' when 'message:appUser' json['messages'].each do |message| - self.parse_message(message, json['app']['_id'], json) SmoochTiplineMessageWorker.perform_async(message, json) + self.parse_message(message, json['app']['_id'], json) end true when 'message:delivery:failure' @@ -570,12 +570,12 @@ def self.process_menu_option(message, state, app_id) end # ...if nothing is matched, try using the NLU feature if state != 'query' - options = SmoochNlu.menu_options_from_message(typed, language, options) + options = SmoochNlu.menu_options_from_message(typed, language, options, uid) unless options.blank? SmoochNlu.process_menu_options(uid, options, message, language, workflow, app_id) return true end - resource = TiplineResource.resource_from_message(typed, language) + resource = TiplineResource.resource_from_message(typed, language, uid) unless resource.nil? CheckStateMachine.new(uid).reset resource = self.send_resource_to_user(uid, workflow, resource.uuid, language) diff --git a/app/models/concerns/tipline_resource_nlu.rb b/app/models/concerns/tipline_resource_nlu.rb index 5d92c469d2..66e299c41b 100644 --- a/app/models/concerns/tipline_resource_nlu.rb +++ b/app/models/concerns/tipline_resource_nlu.rb @@ -32,11 +32,11 @@ def update_resource_keywords(keyword, operation) end module ClassMethods - def resource_from_message(message, language) + def resource_from_message(message, language, uid) context = { context: ALEGRE_CONTEXT_KEY_RESOURCE } - matches = SmoochNlu.alegre_matches_from_message(message, language, context, 'resource_id').collect{ |m| m['key'] } + matches = SmoochNlu.alegre_matches_from_message(message, language, context, 'resource_id', uid).collect{ |m| m['key'] } # Select the top resource that exists resource_id = matches.find { |id| TiplineResource.where(id: id).exists? } Rails.logger.info("[Smooch NLU] [Resource From Message] Resource ID: #{resource_id} | Message: #{message}") diff --git a/config/config.yml.example b/config/config.yml.example index 35167e9f3c..e34a778e2c 100644 --- a/config/config.yml.example +++ b/config/config.yml.example @@ -259,12 +259,14 @@ development: &default otel_traces_sampler: otel_custom_sampling_rate: - # Rate limit for tipline submissions, tipline users are blocked after reaching this limit + # Rate limits for tiplines # # OPTIONAL - # When not set, a default number will be used. + # When not set, default values are used. # tipline_user_max_messages_per_day: 1500 + nlu_global_rate_limit: 100 + nlu_user_rate_limit: 30 devise_maximum_attempts: 5 devise_unlock_accounts_after: 1 diff --git a/test/lib/smooch_nlu_test.rb b/test/lib/smooch_nlu_test.rb index dddcdec083..86e01015f8 100644 --- a/test/lib/smooch_nlu_test.rb +++ b/test/lib/smooch_nlu_test.rb @@ -110,7 +110,7 @@ def create_team_with_smooch_bot_installed team = create_team_with_smooch_bot_installed SmoochNlu.new(team.slug).disable! Bot::Smooch.get_installation('smooch_id', 'test') - assert_equal [], SmoochNlu.menu_options_from_message('I want to subscribe to the newsletter', 'en', @menu_options) + assert_equal [], SmoochNlu.menu_options_from_message('I want to subscribe to the newsletter', 'en', @menu_options, random_string) end test 'should return a menu option if NLU is enabled' do @@ -120,6 +120,31 @@ def create_team_with_smooch_bot_installed team = create_team_with_smooch_bot_installed SmoochNlu.new(team.slug).enable! Bot::Smooch.get_installation('smooch_id', 'test') - assert_not_nil SmoochNlu.menu_options_from_message('I want to subscribe to the newsletter', 'en', @menu_options) + assert_not_nil SmoochNlu.menu_options_from_message('I want to subscribe to the newsletter', 'en', @menu_options, random_string) + end + + test 'should return empty list of matches if global rate limit is reached' do + Bot::Smooch.stubs(:config).returns({ 'nlu_enabled' => true }) + Bot::Alegre.stubs(:request).never + SmoochNlu.increment_global_counter + SmoochNlu.increment_global_counter + stub_configs({ 'nlu_global_rate_limit' => 1 }) do + assert_equal [], SmoochNlu.alegre_matches_from_message('test', 'en', {}, 'test', 'test') + end + end + + test 'should return empty list of matches if user rate limit is reached' do + Bot::Smooch.stubs(:config).returns({ 'nlu_enabled' => true }) + Bot::Alegre.stubs(:request).never + 2.times { create_tipline_message uid: '123', state: 'received' } + stub_configs({ 'nlu_user_rate_limit' => 1 }) do + assert_equal [], SmoochNlu.alegre_matches_from_message('test', 'en', {}, 'test', '123') + end + end + + test 'should return empty list of matches if exception happens' do + Bot::Smooch.stubs(:config).returns({ 'nlu_enabled' => true }) + Bot::Alegre.stubs(:request).never + assert_equal [], SmoochNlu.alegre_matches_from_message('test', 'en', {}, 'test', 'test') end end diff --git a/test/models/bot/smooch_8_test.rb b/test/models/bot/smooch_8_test.rb index 9e2b985700..489ea3e689 100644 --- a/test/models/bot/smooch_8_test.rb +++ b/test/models/bot/smooch_8_test.rb @@ -3,6 +3,7 @@ class Bot::Smooch8Test < ActiveSupport::TestCase def setup + WebMock.disable_net_connect! allow: /#{CheckConfig.get('elasticsearch_host')}|#{CheckConfig.get('storage_endpoint')}/ end def teardown diff --git a/test/models/bot_user_2_test.rb b/test/models/bot_user_2_test.rb index 4198df57e1..2311497080 100644 --- a/test/models/bot_user_2_test.rb +++ b/test/models/bot_user_2_test.rb @@ -127,4 +127,12 @@ class BotUser2Test < ActiveSupport::TestCase b.call({}) end end + + test "should capture error if bot can't be called" do + Bot::Alegre.stubs(:run).raises(StandardError) + b = create_bot_user login: 'alegre' + assert_nothing_raised do + b.call({}) + end + end end From f5debd55df3ef892cf3400f8bc46335550dd4e1b Mon Sep 17 00:00:00 2001 From: Mohamed El-Sawy Date: Sun, 28 Jan 2024 08:01:42 +0200 Subject: [PATCH 4/4] CV2-2648 Refactor Tipline requests (#1743) * CV2-2648: add tipline request model * CV2-2648: replace smooch_data queries with TiplineRequest * CV2-2648: fix cached fields * CV2-2648: remove unneeded migration * CV2-2648: fix syntex * CV2-2648: fix tests * CV2-2648: rename methods and args * CV2-2648: fix seed file and change TiplineRequest column types * CV2-2648: fix check statistics * CV2-2648: fix tests * CV2-2648: add more tests * CV2-2648: test coverage * CV2-2648: apply PR comments * CV2-2648: fix tests * CV2-2648: add rake task to migrate item requests * CV2-2648: add more rake tasks * CV2-2648: cleanup * Debug hanging test * Better debugging for hanging test * Looks like this test is hanging - commenting to be sure * CV2-2648: set start value for TiplineRequest.id and fix tests * Trying to fix tests * Trying to debug failing test * Revert "Trying to debug failing test" This reverts commit b30cb07ceb2ffc9743297bfde60fbba8c44c882b. * Revert "Trying to fix tests" This reverts commit 45946b82b616ae01163eb14fc3484441d1080d23. * Fixing tests * CV2-2648: apply PR comments * CV2-2648: allow rake task to accept args(slug and batch size) * CV2-2648: Get reports received based on smooch_report_received_at field * Fixing a bug * CV2-2648: fix average time for statistics data * CV2-2648: remove unneeded file --------- Co-authored-by: Caio <117518+caiosba@users.noreply.github.com> --- .../mutations/tipline_message_mutations.rb | 4 +- app/graph/types/tipline_request_type.rb | 8 +- app/models/ability.rb | 6 +- app/models/bot/smooch.rb | 50 +- app/models/cluster.rb | 17 +- .../concerns/project_media_associations.rb | 1 + .../concerns/project_media_cached_fields.rb | 48 +- app/models/concerns/smooch_fields.rb | 110 - app/models/concerns/smooch_messages.rb | 84 +- app/models/concerns/team_associations.rb | 1 + app/models/concerns/team_rules.rb | 5 +- app/models/dynamic_annotation/field.rb | 23 +- app/models/project_media.rb | 22 +- app/models/tipline_request.rb | 147 ++ app/models/tipline_resource.rb | 1 + app/models/user.rb | 1 + config/initializers/report_designer.rb | 2 +- config/locales/en.yml | 1 + ...128175927_create_smooch_annotation_type.rb | 8 - ...ch_received_field_to_smooch_annotations.rb | 11 - ...117131952_add_conversation_id_to_smooch.rb | 10 - ...add_request_type_and_resource_to_smooch.rb | 11 - ...mooch_sent_fields_to_smooch_annotations.rb | 12 - ...31026162554_add_smooch_message_id_field.rb | 11 - .../20231122054128_create_tipline_requests.rb | 27 + db/schema.rb | 29 + db/seeds.rb | 13 +- lib/check_statistics.rb | 55 +- lib/relay.idl | 247 +- lib/sample_data.rb | 15 + ...231122054128_migrate_tipline_requests.rake | 347 +++ public/relay.json | 2134 +++-------------- test/controllers/elastic_search_10_test.rb | 5 +- test/controllers/elastic_search_3_test.rb | 9 +- test/controllers/elastic_search_8_test.rb | 6 +- .../controllers/graphql_controller_10_test.rb | 8 +- test/controllers/graphql_controller_3_test.rb | 12 +- test/models/ability_test.rb | 19 + test/models/bot/alegre_3_test.rb | 10 +- test/models/bot/alegre_test.rb | 11 + test/models/bot/smooch_2_test.rb | 15 +- test/models/bot/smooch_3_test.rb | 43 +- test/models/bot/smooch_4_test.rb | 85 +- test/models/bot/smooch_6_test.rb | 31 +- test/models/bot/smooch_7_test.rb | 69 +- test/models/bot/smooch_8_test.rb | 27 - test/models/bot/smooch_rules_test.rb | 12 + test/models/bot/smooch_test.rb | 72 +- test/models/cluster_test.rb | 7 +- test/models/dynamic_annotation/field_test.rb | 8 +- test/models/project_media_2_test.rb | 15 +- test/models/project_media_6_test.rb | 9 +- test/models/tipline_request_test.rb | 56 + test/test_helper.rb | 14 +- 54 files changed, 1441 insertions(+), 2563 deletions(-) delete mode 100644 app/models/concerns/smooch_fields.rb create mode 100644 app/models/tipline_request.rb delete mode 100644 db/migrate/20190128175927_create_smooch_annotation_type.rb delete mode 100644 db/migrate/20201016004453_add_smooch_received_field_to_smooch_annotations.rb delete mode 100644 db/migrate/20201117131952_add_conversation_id_to_smooch.rb delete mode 100644 db/migrate/20201207042158_add_request_type_and_resource_to_smooch.rb delete mode 100644 db/migrate/20231011090947_add_smooch_sent_fields_to_smooch_annotations.rb delete mode 100644 db/migrate/20231026162554_add_smooch_message_id_field.rb create mode 100644 db/migrate/20231122054128_create_tipline_requests.rb create mode 100644 lib/tasks/migrate/20231122054128_migrate_tipline_requests.rake delete mode 100644 test/models/bot/smooch_8_test.rb create mode 100644 test/models/tipline_request_test.rb diff --git a/app/graph/mutations/tipline_message_mutations.rb b/app/graph/mutations/tipline_message_mutations.rb index 686444dbd9..a5ca9f89be 100644 --- a/app/graph/mutations/tipline_message_mutations.rb +++ b/app/graph/mutations/tipline_message_mutations.rb @@ -6,10 +6,10 @@ class Send < Mutations::BaseMutation field :success, GraphQL::Types::Boolean, null: true def resolve(in_reply_to_id: nil, message: nil) - request = Annotation.find(in_reply_to_id).load + request = TiplineRequest.find(in_reply_to_id) ability = context[:ability] || Ability.new success = false - if Team.current&.id && User.current&.id && ability.can?(:send, TiplineMessage.new(team: Team.current)) && request.annotated.team_id == Team.current.id + if Team.current&.id && User.current&.id && ability.can?(:send, TiplineMessage.new(team: Team.current)) && request.team_id == Team.current.id success = Bot::Smooch.reply_to_request_with_custom_message(request, message) end { success: success } diff --git a/app/graph/types/tipline_request_type.rb b/app/graph/types/tipline_request_type.rb index 22f41f2c2e..0fa06c8b27 100644 --- a/app/graph/types/tipline_request_type.rb +++ b/app/graph/types/tipline_request_type.rb @@ -4,10 +4,9 @@ class TiplineRequestType < DefaultObject implements GraphQL::Types::Relay::Node field :dbid, GraphQL::Types::Int, null: true - field :value_json, JsonStringType, null: true - field :annotation, AnnotationType, null: true - field :annotation_id, GraphQL::Types::Int, null: true - field :associated_graphql_id, GraphQL::Types::String, null: true + field :associated_id, GraphQL::Types::Int, null: true + field :associated_type, GraphQL::Types::String, null: true + field :smooch_data, JsonStringType, null: true field :smooch_user_slack_channel_url, GraphQL::Types::String, null: true field :smooch_user_external_identifier, GraphQL::Types::String, null: true field :smooch_report_received_at, GraphQL::Types::Int, null: true @@ -16,4 +15,5 @@ class TiplineRequestType < DefaultObject field :smooch_report_sent_at, GraphQL::Types::Int, null: true field :smooch_report_correction_sent_at, GraphQL::Types::Int, null: true field :smooch_request_type, GraphQL::Types::String, null: true + field :associated_graphql_id, GraphQL::Types::String, null: true end diff --git a/app/models/ability.rb b/app/models/ability.rb index b39f731ce0..b5eb46e822 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -89,7 +89,7 @@ def editor_perms can [:cud], DynamicAnnotation::Field do |obj| obj.annotation.team&.id == @context_team.id end - can [:create, :update, :read, :destroy], [Account, Source, TiplineNewsletter, TiplineResource], :team_id => @context_team.id + can [:create, :update, :read, :destroy], [Account, Source, TiplineNewsletter, TiplineResource, TiplineRequest], :team_id => @context_team.id can [:cud], AccountSource, source: { team: { team_users: { team_id: @context_team.id }}} %w(annotation comment dynamic task tag).each do |annotation_type| can [:cud], annotation_type.classify.constantize do |obj| @@ -135,6 +135,10 @@ def collaborator_perms obj.team&.id == @context_team.id && !obj.annotated_is_trashed? end end + can [:cud], TiplineRequest do |obj| + is_trashed = obj.associated.respond_to?(:archived) && obj.associated.archived == CheckArchivedFlags::FlagCodes::TRASHED + obj.team_id == @context_team.id && !is_trashed + end can [:create, :destroy], Assignment do |obj| type = obj.assigned_type obj = obj.assigned diff --git a/app/models/bot/smooch.rb b/app/models/bot/smooch.rb index 7f0ac223ae..7a495d1865 100644 --- a/app/models/bot/smooch.rb +++ b/app/models/bot/smooch.rb @@ -29,7 +29,6 @@ class CapiUnhandledMessageWarning < MessageDeliveryError; end include SmoochCapi include SmoochStrings include SmoochMenus - include SmoochFields include SmoochLanguage include SmoochBlocking @@ -40,19 +39,18 @@ def report_image self.get_dynamic_annotation('report_design')&.report_design_image_url end - def get_deduplicated_smooch_annotations + def get_deduplicated_tipline_requests uids = [] - annotations = [] + tipline_requests = [] ProjectMedia.where(id: self.related_items_ids).each do |pm| - pm.get_annotations('smooch').find_each do |annotation| - data = JSON.parse(annotation.load.get_field_value('smooch_data')) - uid = data['authorId'] + pm.tipline_requests.find_each do |tr| + uid = tr.tipline_user_uid next if uids.include?(uid) uids << uid - annotations << annotation + tipline_requests << tr end end - annotations + tipline_requests end end @@ -125,7 +123,9 @@ def self.inherit_status_and_send_report(rid) '_id': Digest::MD5.hexdigest([self.action.to_s, Time.now.to_f.to_s].join(':')), authorId: id, type: 'text', - text: message[1] + text: message[1], + source: { type: "whatsapp" }, + language: 'en' }.with_indifferent_access Bot::Smooch.save_message_later(payload, app_id) end @@ -602,10 +602,11 @@ def self.user_received_report(message) original = begin JSON.parse(original) rescue {} end if original['fallback_template'] =~ /report/ pmids = ProjectMedia.find(original['project_media_id']).related_items_ids - DynamicAnnotation::Field.joins(:annotation).where(field_name: 'smooch_data', 'annotations.annotated_type' => 'ProjectMedia', 'annotations.annotated_id' => pmids).where("value_json ->> 'authorId' = ?", message['appUser']['_id']).each do |f| - a = f.annotation.load - a.set_fields = { smooch_report_received: Time.now.to_i }.to_json - a.save! + TiplineRequest.where(associated_type: 'ProjectMedia', associated_id: pmids, tipline_user_uid: message['appUser']['_id']).find_each do |tr| + field_name = tr.smooch_report_received_at == 0 ? 'smooch_report_received_at' : 'smooch_report_update_received_at' + tr.send("#{field_name}=", Time.now.to_i) + tr.skip_check_ability = true + tr.save! end end end @@ -934,15 +935,15 @@ def self.send_report_to_users(pm, action) report = parent.get_annotations('report_design').last&.load return if report.nil? last_published_at = report.get_field_value('last_published').to_i - parent.get_deduplicated_smooch_annotations.each do |annotation| - data = JSON.parse(annotation.load.get_field_value('smooch_data')) + parent.get_deduplicated_tipline_requests.each do |tipline_request| + data = tipline_request.smooch_data self.get_installation(self.installation_setting_id_keys, data['app_id']) if self.config.blank? - self.send_correction_to_user(data, parent, annotation, last_published_at, action, report.get_field_value('published_count').to_i) unless self.config['smooch_disabled'] + self.send_correction_to_user(data, parent, tipline_request, last_published_at, action, report.get_field_value('published_count').to_i) unless self.config['smooch_disabled'] end end - def self.send_correction_to_user(data, pm, annotation, last_published_at, action, published_count = 0) - subscribed_at = annotation.created_at + def self.send_correction_to_user(data, pm, tipline_request, last_published_at, action, published_count = 0) + subscribed_at = tipline_request.created_at self.get_platform_from_message(data) uid = data['authorId'] lang = data['language'] @@ -959,10 +960,9 @@ def self.send_correction_to_user(data, pm, annotation, last_published_at, action self.send_report_to_user(uid, data, pm, lang, 'fact_check_report') end unless field_name.blank? - annotation = annotation.load - annotation.skip_check_ability = true - annotation.set_fields = { "#{field_name}": Time.now.to_i }.to_json - annotation.save! + tipline_request.skip_check_ability = true + tipline_request.send("#{field_name}=", Time.now.to_i) + tipline_request.save! end end @@ -1017,11 +1017,11 @@ def self.send_report_from_parent_to_child(parent_id, target_id) parent = ProjectMedia.where(id: parent_id).last child = ProjectMedia.where(id: target_id).last return if parent.nil? || child.nil? - child.get_annotations('smooch').find_each do |annotation| - data = JSON.parse(annotation.load.get_field_value('smooch_data')) + child.tipline_requests.find_each do |tr| + data = tr.smooch_data self.get_platform_from_message(data) self.get_installation(self.installation_setting_id_keys, data['app_id']) if self.config.blank? - self.send_report_to_user(data['authorId'], data, parent, data['language'], 'fact_check_report') + self.send_report_to_user(tr.tipline_user_uid, data, parent, tr.language, 'fact_check_report') end end diff --git a/app/models/cluster.rb b/app/models/cluster.rb index 8a2183e197..fdcb162309 100644 --- a/app/models/cluster.rb +++ b/app/models/cluster.rb @@ -85,9 +85,9 @@ def claim_descriptions recalculate: :recalculate_requests_count, update_on: [ { - model: Dynamic, - if: proc { |d| d.annotation_type == 'smooch' && d.annotated_type == 'ProjectMedia' }, - affected_ids: proc { |d| ProjectMedia.where(id: d.annotated.related_items_ids).group(:cluster_id).count.keys.reject{ |cid| cid.nil? } }, + model: TiplineRequest, + if: proc { |tr| tr.associated_type == 'ProjectMedia' }, + affected_ids: proc { |tr| ProjectMedia.where(id: tr.associated.related_items_ids).group(:cluster_id).count.keys.reject{ |cid| cid.nil? } }, events: { create: :cached_field_cluster_requests_count_create, destroy: :cached_field_cluster_requests_count_destroy @@ -154,14 +154,3 @@ def update_elasticsearch ElasticSearchWorker.perform_in(1.second, YAML::dump(model), YAML::dump(options), 'update_doc') end end - - -Dynamic.class_eval do - def cached_field_cluster_requests_count_create(target) - target.requests_count + 1 - end - - def cached_field_cluster_requests_count_destroy(target) - target.requests_count - 1 - end -end diff --git a/app/models/concerns/project_media_associations.rb b/app/models/concerns/project_media_associations.rb index e2382791f5..a96522ad35 100644 --- a/app/models/concerns/project_media_associations.rb +++ b/app/models/concerns/project_media_associations.rb @@ -19,6 +19,7 @@ module ProjectMediaAssociations has_one :claim_description, dependent: :destroy belongs_to :cluster, counter_cache: :project_medias_count, optional: true belongs_to :source, optional: true + has_many :tipline_requests, as: :associated has_annotations end end diff --git a/app/models/concerns/project_media_cached_fields.rb b/app/models/concerns/project_media_cached_fields.rb index d42b7b2d9f..7da264558f 100644 --- a/app/models/concerns/project_media_cached_fields.rb +++ b/app/models/concerns/project_media_cached_fields.rb @@ -117,9 +117,9 @@ def title_or_description_update recalculate: :recalculate_requests_count, update_on: [ { - model: Dynamic, - if: proc { |d| d.annotation_type == 'smooch' && d.annotated_type == 'ProjectMedia' }, - affected_ids: proc { |d| [d.annotated_id] }, + model: TiplineRequest, + if: proc { |tr| tr.associated_type == 'ProjectMedia' }, + affected_ids: proc { |tr| [tr.associated_id] }, events: { create: :recalculate, destroy: :recalculate, @@ -133,9 +133,9 @@ def title_or_description_update recalculate: :recalculate_demand, update_on: [ { - model: Dynamic, - if: proc { |d| d.annotation_type == 'smooch' && d.annotated_type == 'ProjectMedia' }, - affected_ids: proc { |d| d.annotated.related_items_ids }, + model: TiplineRequest, + if: proc { |tr| tr.associated_type == 'ProjectMedia' }, + affected_ids: proc { |tr| tr.associated.related_items_ids }, events: { create: :recalculate, } @@ -158,9 +158,9 @@ def title_or_description_update recalculate: :recalculate_last_seen, update_on: [ { - model: Dynamic, - if: proc { |d| d.annotation_type == 'smooch' && d.annotated_type == 'ProjectMedia' }, - affected_ids: proc { |d| d.annotated&.related_items_ids.to_a }, + model: TiplineRequest, + if: proc { |tr| tr.associated_type == 'ProjectMedia' }, + affected_ids: proc { |tr| tr.associated&.related_items_ids.to_a }, events: { create: :recalculate, } @@ -459,9 +459,9 @@ def title_or_description_update recalculate: :recalculate_positive_tipline_search_results_count, update_on: [ { - model: DynamicAnnotation::Field, - if: proc { |f| f.field_name == 'smooch_request_type' && f.value == 'relevant_search_result_requests' }, - affected_ids: proc { |f| [f.annotation&.annotated_id.to_i] }, + model: TiplineRequest, + if: proc { |tr| tr.smooch_request_type == 'relevant_search_result_requests' }, + affected_ids: proc { |tr| [tr.associated_id] }, events: { save: :recalculate, destroy: :recalculate, @@ -474,9 +474,9 @@ def title_or_description_update recalculate: :recalculate_tipline_search_results_count, update_on: [ { - model: DynamicAnnotation::Field, - if: proc { |f| f.field_name == 'smooch_request_type' && ['relevant_search_result_requests', 'irrelevant_search_result_requests', 'timeout_search_requests'].include?(f.value) }, - affected_ids: proc { |f| [f.annotation&.annotated_id.to_i] }, + model: TiplineRequest, + if: proc { |tr| ['relevant_search_result_requests', 'irrelevant_search_result_requests', 'timeout_search_requests'].include?(tr.smooch_request_type) }, + affected_ids: proc { |tr| [tr.associated_id] }, events: { save: :recalculate, destroy: :recalculate, @@ -507,7 +507,7 @@ def recalculate_related_count end def recalculate_requests_count - Dynamic.where(annotation_type: 'smooch', annotated_id: self.id).count + TiplineRequest.where(associated_type: 'ProjectMedia', associated_id: self.id).count end def recalculate_demand @@ -517,10 +517,10 @@ def recalculate_demand end def recalculate_last_seen - # If it’s a main/parent item, last_seen is related to any request (smooch annotation) to that own ProjectMedia or any similar/child ProjectMedia - # If it’s not a main item (so, single or child, a.k.a. “confirmed match” or “suggestion”), then last_seen is related only to smooch annotations (requests) related to that ProjectMedia. + # If it’s a main/parent item, last_seen is related to any tipline request to that own ProjectMedia or any similar/child ProjectMedia + # If it’s not a main item (so, single or child, a.k.a. “confirmed match” or “suggestion”), then last_seen is related only to tipline requests related to that ProjectMedia. ids = self.is_parent ? self.related_items_ids : self.id - v1 = Dynamic.where(annotation_type: 'smooch', annotated_id: ids).order('created_at DESC').first&.created_at || 0 + v1 = TiplineRequest.where(associated_type: 'ProjectMedia', associated_id: ids).order('created_at DESC').first&.created_at || 0 v2 = ProjectMedia.where(id: ids).order('created_at DESC').first&.created_at || 0 [v1, v2].max.to_i end @@ -655,16 +655,12 @@ def cached_field_published_by_es(value) end def recalculate_positive_tipline_search_results_count - DynamicAnnotation::Field.where(annotation_type: 'smooch',field_name: 'smooch_request_type', value: 'relevant_search_result_requests') - .joins('INNER JOIN annotations a ON a.id = dynamic_annotation_fields.annotation_id') - .where('a.annotated_type = ? AND a.annotated_id = ?', 'ProjectMedia', self.id).count + TiplineRequest.where(associated_type: 'ProjectMedia', associated_id: self.id, smooch_request_type: 'relevant_search_result_requests').count end def recalculate_tipline_search_results_count - DynamicAnnotation::Field.where(annotation_type: 'smooch',field_name: 'smooch_request_type') - .where('value IN (?)', ['"relevant_search_result_requests"', '"irrelevant_search_result_requests"', '"timeout_search_requests"']) - .joins('INNER JOIN annotations a ON a.id = dynamic_annotation_fields.annotation_id') - .where('a.annotated_type = ? AND a.annotated_id = ?', 'ProjectMedia', self.id).count + types = ["relevant_search_result_requests", "irrelevant_search_result_requests", "timeout_search_requests"] + TiplineRequest.where(associated_type: 'ProjectMedia', associated_id: self.id, smooch_request_type: types).count end end diff --git a/app/models/concerns/smooch_fields.rb b/app/models/concerns/smooch_fields.rb deleted file mode 100644 index 51ea089455..0000000000 --- a/app/models/concerns/smooch_fields.rb +++ /dev/null @@ -1,110 +0,0 @@ -require 'active_support/concern' - -module SmoochFields - extend ActiveSupport::Concern - - module ClassMethods - ::DynamicAnnotation::Field.class_eval do - def smooch_user_slack_channel_url - Concurrent::Future.execute(executor: CheckGraphql::POOL) do - return unless self.field_name == 'smooch_data' - slack_channel_url = '' - data = self.value_json - unless data.nil? - key = "SmoochUserSlackChannelUrl:Team:#{self.team.id}:#{data['authorId']}" - slack_channel_url = Rails.cache.read(key) - if slack_channel_url.blank? - # obj = self.associated - obj = self.annotation.annotated - slack_channel_url = get_slack_channel_url(obj, data) - Rails.cache.write(key, slack_channel_url) unless slack_channel_url.blank? - end - end - slack_channel_url - end - end - - def smooch_user_external_identifier - Concurrent::Future.execute(executor: CheckGraphql::POOL) do - return unless self.field_name == 'smooch_data' - data = self.value_json - Rails.cache.fetch("smooch:user:external_identifier:#{data['authorId']}") do - field = DynamicAnnotation::Field.where('field_name = ? AND dynamic_annotation_fields_value(field_name, value) = ?', 'smooch_user_id', data['authorId'].to_json).last - return '' if field.nil? - user = JSON.parse(field.annotation.load.get_field_value('smooch_user_data')).with_indifferent_access[:raw][:clients][0] - case user[:platform] - when 'whatsapp' - user[:displayName] - when 'telegram', 'instagram' - '@' + user[:raw][:username].to_s - when 'messenger', 'viber', 'line' - user[:externalId] - when 'twitter' - '@' + user[:raw][:screen_name] - else - '' - end - end - end - end - - def smooch_report_received_at - Concurrent::Future.execute(executor: CheckGraphql::POOL) do - begin self.annotation.load.get_field_value('smooch_report_received').to_i rescue nil end - end - end - - def smooch_report_update_received_at - Concurrent::Future.execute(executor: CheckGraphql::POOL) do - begin - field = self.annotation.load.get_field('smooch_report_received') - field.created_at != field.updated_at ? field.value.to_i : nil - rescue - nil - end - end - end - - def smooch_report_sent_at - Concurrent::Future.execute(executor: CheckGraphql::POOL) do - begin self.annotation.load.get_field_value('smooch_report_sent_at').to_i rescue nil end - end - end - - def smooch_report_correction_sent_at - Concurrent::Future.execute(executor: CheckGraphql::POOL) do - begin self.annotation.load.get_field_value('smooch_report_correction_sent_at').to_i rescue nil end - end - end - - def smooch_request_type - Concurrent::Future.execute(executor: CheckGraphql::POOL) do - begin self.annotation.load.get_field_value('smooch_request_type') rescue nil end - end - end - - def smooch_user_request_language - Concurrent::Future.execute(executor: CheckGraphql::POOL) do - return '' unless self.field_name == 'smooch_data' - self.value_json['language'].to_s - end - end - - private - - def get_slack_channel_url(obj, data) - slack_channel_url = nil - tid = obj.team_id - smooch_user_data = DynamicAnnotation::Field.where(field_name: 'smooch_user_id', annotation_type: 'smooch_user') - .where('dynamic_annotation_fields_value(field_name, value) = ?', data['authorId'].to_json) - .joins("INNER JOIN annotations a ON a.id = dynamic_annotation_fields.annotation_id") - .where("a.annotated_type = ? AND a.annotated_id = ?", 'Team', tid).last - unless smooch_user_data.nil? - field_value = DynamicAnnotation::Field.where(field_name: 'smooch_user_slack_channel_url', annotation_type: 'smooch_user', annotation_id: smooch_user_data.annotation_id).last - slack_channel_url = field_value.value unless field_value.nil? - end - slack_channel_url - end - end - end -end diff --git a/app/models/concerns/smooch_messages.rb b/app/models/concerns/smooch_messages.rb index e9bc5ea8a9..caea4ef6bd 100644 --- a/app/models/concerns/smooch_messages.rb +++ b/app/models/concerns/smooch_messages.rb @@ -330,86 +330,75 @@ def default_archived_flag Bot::Alegre.team_has_alegre_bot_installed?(team_id) ? CheckArchivedFlags::FlagCodes::PENDING_SIMILARITY_ANALYSIS : CheckArchivedFlags::FlagCodes::NONE end - def save_message(message_json, app_id, author = nil, request_type = 'default_requests', annotated_obj = nil) + def save_message(message_json, app_id, author = nil, request_type = 'default_requests', associated_obj = nil) message = JSON.parse(message_json) self.get_installation(self.installation_setting_id_keys, app_id) Team.current = Team.where(id: self.config['team_id']).last - annotated = nil + associated = nil if ['default_requests', 'timeout_requests', 'irrelevant_search_result_requests'].include?(request_type) message['archived'] = ['default_requests', 'irrelevant_search_result_requests'].include?(request_type) ? self.default_archived_flag : CheckArchivedFlags::FlagCodes::UNCONFIRMED - annotated = self.create_project_media_from_message(message) + associated = self.create_project_media_from_message(message) elsif ['menu_options_requests', 'resource_requests'].include?(request_type) - annotated = annotated_obj + associated = associated_obj elsif ['relevant_search_result_requests', 'timeout_search_requests'].include?(request_type) message['archived'] = (request_type == 'relevant_search_result_requests' ? self.default_archived_flag : CheckArchivedFlags::FlagCodes::UNCONFIRMED) - annotated = self.create_project_media_from_message(message) - if annotated != annotated_obj && annotated.is_a?(ProjectMedia) - Relationship.create(relationship_type: Relationship.suggested_type, source: annotated_obj, target: annotated, user: BotUser.smooch_user) + associated = self.create_project_media_from_message(message) + if associated != associated_obj && associated.is_a?(ProjectMedia) + Relationship.create(relationship_type: Relationship.suggested_type, source: associated_obj, target: associated, user: BotUser.smooch_user) end end - return if annotated.nil? + return if associated.nil? # Remember that we received this message. hash = self.message_hash(message) - Rails.cache.write("smooch:message:#{hash}", annotated.id) + Rails.cache.write("smooch:message:#{hash}", associated.id) - self.smooch_save_annotations(message, annotated, app_id, author, request_type, annotated_obj) + self.smooch_save_tipline_request(message, associated, app_id, author, request_type, associated_obj) # If item is published (or parent item), send a report right away self.get_platform_from_message(message) - self.send_report_to_user(message['authorId'], message, annotated, message['language'], 'fact_check_report') if self.should_try_to_send_report?(request_type, annotated) + self.send_report_to_user(message['authorId'], message, associated, message['language'], 'fact_check_report') if self.should_try_to_send_report?(request_type, associated) end - def smooch_save_annotations(message, annotated, app_id, author, request_type, annotated_obj) - self.create_smooch_request(annotated, message, app_id, author) - self.create_smooch_resources_and_type(annotated, annotated_obj, author, request_type) - end - - def create_smooch_request(annotated, message, app_id, author) - fields = { smooch_data: message.merge({ app_id: app_id }).to_json } + def smooch_save_tipline_request(message, associated, app_id, author, request_type, associated_obj) + fields = { smooch_data: message.merge({ app_id: app_id }) } result = self.smooch_api_get_messages(app_id, message['authorId']) fields[:smooch_conversation_id] = result.conversation.id unless result.nil? || result.conversation.nil? fields[:smooch_message_id] = message['_id'] - self.create_smooch_annotations(annotated, author, fields) + fields[:smooch_request_type] = request_type + fields[:smooch_resource_id] = associated_obj.id if request_type == 'resource_requests' && !associated_obj.nil? + self.create_tipline_requests(associated, author, fields) # Update channel values for ProjectMedia items - if annotated.class.name == 'ProjectMedia' + if associated.class.name == 'ProjectMedia' channel_value = self.get_smooch_channel(message) unless channel_value.blank? - others = annotated.channel.with_indifferent_access[:others] || [] - annotated.channel[:others] = others.concat([channel_value]).uniq - annotated.skip_check_ability = true - annotated.save! + others = associated.channel.with_indifferent_access[:others] || [] + associated.channel[:others] = others.concat([channel_value]).uniq + associated.skip_check_ability = true + associated.save! end end end - def create_smooch_resources_and_type(annotated, annotated_obj, author, request_type) - fields = { smooch_request_type: request_type } - fields[:smooch_resource_id] = annotated_obj.id if request_type == 'resource_requests' && !annotated_obj.nil? - self.create_smooch_annotations(annotated, author, fields, true) - end - - def create_smooch_annotations(annotated, author, fields, attach_to = false) + def create_tipline_requests(associated, author, fields) # TODO: By Sawy - Should handle User.current value # In this case User.current was reset by SlackNotificationWorker worker # Quick fix - assigning it again using annotated object and reset its value at the end of creation current_user = User.current User.current = author - User.current = annotated.user if User.current.nil? && annotated.respond_to?(:user) - a = nil - a = Dynamic.where(annotation_type: 'smooch', annotated_id: annotated.id, annotated_type: annotated.class.name).last if attach_to - if a.nil? - a = Dynamic.new - a.annotation_type = 'smooch' - a.annotated = annotated + User.current = associated.user if User.current.nil? && associated.respond_to?(:user) + fields = fields.with_indifferent_access + tr = TiplineRequest.new + tr.associated = associated + tr.skip_check_ability = true + tr.skip_notifications = true + tr.disable_es_callbacks = Rails.env.to_s == 'test' + fields.each do |k, v| + tr.send("#{k}=", v) if tr.respond_to?("#{k}=") end - a.skip_check_ability = true - a.skip_notifications = true - a.disable_es_callbacks = Rails.env.to_s == 'test' - a.set_fields = fields.to_json begin - a.save! + tr.save! rescue ActiveRecord::RecordNotUnique Rails.logger.info('[Smooch Bot] Not storing tipline request because it already exists.') end @@ -426,8 +415,8 @@ def send_message_on_status_change(pm_id, status, request_actor_session_id = nil) return if pm.nil? requestors_count = 0 parent = Relationship.where(target_id: pm.id).last&.source || pm - parent.get_deduplicated_smooch_annotations.each do |annotation| - data = JSON.parse(annotation.load.get_field_value('smooch_data')) + parent.get_deduplicated_tipline_requests.each do |tr| + data = tr.smooch_data self.get_installation(self.installation_setting_id_keys, data['app_id']) if self.config.blank? message = parent.team.get_status_message_for_language(status, data['language']) unless message.blank? @@ -449,8 +438,9 @@ def send_message_to_user_on_timeout(uid, language) end def reply_to_request_with_custom_message(request, message) - data = JSON.parse(request.get_field_value('smooch_data')) - self.send_custom_message_to_user(request.annotated.team, data['authorId'], data['received'], message, data['language']) + data = request.smooch_data + team = Team.find_by_id(request.team_id) + self.send_custom_message_to_user(team, request.tipline_user_uid, data['received'], message, request.language) end def send_custom_message_to_user(team, uid, timestamp, message, language) diff --git a/app/models/concerns/team_associations.rb b/app/models/concerns/team_associations.rb index 2f506787e2..1e839fddd8 100644 --- a/app/models/concerns/team_associations.rb +++ b/app/models/concerns/team_associations.rb @@ -20,6 +20,7 @@ module TeamAssociations has_many :monthly_team_statistics # No "dependent: :destroy" because we want to retain statistics has_many :tipline_messages has_many :tipline_newsletters + has_many :tipline_requests, as: :associated has_annotations end diff --git a/app/models/concerns/team_rules.rb b/app/models/concerns/team_rules.rb index 600d228829..cfe7fac3e2 100644 --- a/app/models/concerns/team_rules.rb +++ b/app/models/concerns/team_rules.rb @@ -50,10 +50,7 @@ def text_contains_keyword(text, value) def get_smooch_message(pm) smooch_message = pm.smooch_message - if smooch_message.nil? - smooch_message = begin JSON.parse(pm.get_annotations('smooch').last.load.get_field_value('smooch_data').to_s) rescue {} end - end - smooch_message + smooch_message.nil? ? pm.tipline_requests.last.smooch_data : smooch_message end def title_matches_regexp(pm, value, _rule_id) diff --git a/app/models/dynamic_annotation/field.rb b/app/models/dynamic_annotation/field.rb index 04547423ae..376f6e5ffa 100644 --- a/app/models/dynamic_annotation/field.rb +++ b/app/models/dynamic_annotation/field.rb @@ -100,10 +100,7 @@ def method_suggestions(prefix) def index_field_elastic_search(op) return if self.disable_es_callbacks || RequestStore.store[:disable_es_callbacks] obj = self.annotation&.project_media - unless obj.nil? - apply_field_index(obj, op) - apply_nested_field_index(obj, op) - end + apply_field_index(obj, op) unless obj.nil? end def apply_field_index(obj, op) @@ -119,22 +116,4 @@ def apply_field_index(obj, op) end obj.update_elasticsearch_doc(data.keys, data, obj.id, true) unless data.blank? end - - def apply_nested_field_index(obj, op) - if self.field_name == 'smooch_data' - if op == 'destroy' - destroy_es_items('requests', 'destroy_doc_nested', obj.id) - else - identifier = begin self.smooch_user_external_identifier&.value rescue self.smooch_user_external_identifier end - data = { - 'username' => self.value_json['name'], - 'identifier' => identifier&.gsub(/[[:space:]|-]/, ''), - 'content' => self.value_json['text'], - 'language' => self.value_json['language'], - } - options = { op: op, pm_id: obj.id, nested_key: 'requests', keys: data.keys, data: data, skip_get_data: true } - self.add_update_nested_obj(options) - end - end - end end diff --git a/app/models/project_media.rb b/app/models/project_media.rb index e3772a41f8..9c0d431ccd 100644 --- a/app/models/project_media.rb +++ b/app/models/project_media.rb @@ -385,8 +385,7 @@ def version_metadata(_changes) def get_requests # Get related items for parent item pm_ids = Relationship.confirmed_parent(self).id == self.id ? self.related_items_ids : [self.id] - sm_ids = Annotation.where(annotation_type: 'smooch', annotated_type: 'ProjectMedia', annotated_id: pm_ids).map(&:id) - sm_ids.blank? ? [] : DynamicAnnotation::Field.where(annotation_id: sm_ids, field_name: 'smooch_data') + TiplineRequest.where(associated_type: 'ProjectMedia', associated_id: pm_ids) end def apply_rules_and_actions_on_update @@ -479,21 +478,14 @@ def add_nested_objects(ms) ms.attributes[:assigned_user_ids] = assignments_uids.uniq # 'requests' requests = [] - fields = DynamicAnnotation::Field.joins(:annotation) - .where( - field_name: 'smooch_data', - 'annotations.annotated_id' => self.id, - 'annotations.annotation_type' => 'smooch', - 'annotations.annotated_type' => 'ProjectMedia' - ) - fields.each do |field| - identifier = begin field.smooch_user_external_identifier&.value rescue field.smooch_user_external_identifier end + TiplineRequest.where(associated_type: 'ProjectMedia', associated_id: self.id).each do |tr| + identifier = begin tr.smooch_user_external_identifier&.value rescue tr.smooch_user_external_identifier end requests << { - id: field.id, - username: field.value_json['name'], + id: tr.id, + username: tr.smooch_data['name'], identifier: identifier&.gsub(/[[:space:]|-]/, ''), - content: field.value_json['text'], - language: field.value_json['language'], + content: tr.smooch_data['text'], + language: tr.language, } end ms.attributes[:requests] = requests diff --git a/app/models/tipline_request.rb b/app/models/tipline_request.rb new file mode 100644 index 0000000000..0bb26320e2 --- /dev/null +++ b/app/models/tipline_request.rb @@ -0,0 +1,147 @@ +class TiplineRequest < ApplicationRecord + include CheckElasticSearch + + belongs_to :associated, polymorphic: true + belongs_to :user, optional: true + + before_validation :set_team_and_user, :set_smooch_data_fields, on: :create + + validates_presence_of :smooch_request_type, :language, :platform + validate :platform_allowed_values + + def self.request_types + %w(default_requests timeout_requests relevant_search_result_requests resource_requests irrelevant_search_result_requests timeout_search_requests menu_options_requests) + end + validates_inclusion_of :smooch_request_type, in: TiplineRequest.request_types + + after_commit :add_elasticsearch_field, on: :create + after_commit :update_elasticsearch_field, on: :update + after_commit :destroy_elasticsearch_field, on: :destroy + + def smooch_user_slack_channel_url + return if self.smooch_data.blank? + slack_channel_url = '' + data = self.smooch_data + unless data.nil? + key = "SmoochUserSlackChannelUrl:Team:#{self.team_id}:#{data['authorId']}" + slack_channel_url = Rails.cache.read(key) + if slack_channel_url.blank? + obj = self.associated + slack_channel_url = get_slack_channel_url(obj, data) + Rails.cache.write(key, slack_channel_url) unless slack_channel_url.blank? + end + end + slack_channel_url + end + + def smooch_user_external_identifier + return if self.tipline_user_uid.blank? + Rails.cache.fetch("smooch:user:external_identifier:#{self.tipline_user_uid}") do + field = DynamicAnnotation::Field.where('field_name = ? AND dynamic_annotation_fields_value(field_name, value) = ?', 'smooch_user_id', self.tipline_user_uid.to_json).last + return '' if field.nil? + smooch_user_data = JSON.parse(field.annotation.load.get_field_value('smooch_user_data')).with_indifferent_access + user = smooch_user_data&.dig('raw', 'clients', 0) || {} + case user[:platform] + when 'whatsapp' + user[:displayName] + when 'telegram', 'instagram' + '@' + user[:raw][:username].to_s + when 'messenger', 'viber', 'line' + user[:externalId] + when 'twitter' + '@' + user[:raw][:screen_name] + else + '' + end + end + end + + def smooch_user_request_language + self.language.to_s + end + + def cached_field_cluster_requests_count_create(target) + target.requests_count + 1 + end + + def cached_field_cluster_requests_count_destroy(target) + target.requests_count - 1 + end + + def associated_graphql_id + Base64.encode64("#{self.associated_type}/#{self.associated_id}") + end + + private + + def set_team_and_user + self.team_id ||= Team.current&.id + self.user_id ||= User.current&.id + end + + def set_smooch_data_fields + unless self.smooch_data.blank? + # Avoid PG::UntranslatableCharacter exception + value = self.smooch_data.to_json.gsub('\u0000', '') + self.smooch_data = JSON.parse(value) + self.tipline_user_uid ||= self.smooch_data.dig('authorId') + self.language ||= self.smooch_data.dig('language') + self.platform ||= self.smooch_data.dig('source', 'type') + end + end + + def platform_allowed_values + allowed_types = Bot::Smooch::SUPPORTED_INTEGRATIONS + unless allowed_types.include?(self.platform) + errors.add(:platform, I18n.t('errors.messages.platform_allowed_values_error', **{ type: self.platform, allowed_types: allowed_types.join(', ') })) + end + end + + def get_slack_channel_url(obj, data) + slack_channel_url = nil + tid = obj.team_id + smooch_user_data = DynamicAnnotation::Field.where(field_name: 'smooch_user_id', annotation_type: 'smooch_user') + .where('dynamic_annotation_fields_value(field_name, value) = ?', data['authorId'].to_json) + .joins("INNER JOIN annotations a ON a.id = dynamic_annotation_fields.annotation_id") + .where("a.annotated_type = ? AND a.annotated_id = ?", 'Team', tid).last + unless smooch_user_data.nil? + field_value = DynamicAnnotation::Field.where(field_name: 'smooch_user_slack_channel_url', annotation_type: 'smooch_user', annotation_id: smooch_user_data.annotation_id).last + slack_channel_url = field_value.value unless field_value.nil? + end + slack_channel_url + end + + def add_elasticsearch_field + index_field_elastic_search('create') + end + + def update_elasticsearch_field + index_field_elastic_search('update') + end + + def destroy_elasticsearch_field + index_field_elastic_search('destroy') + end + + protected + + def index_field_elastic_search(op) + return if self.disable_es_callbacks || RequestStore.store[:disable_es_callbacks] || self.associated_type != 'ProjectMedia' + obj = self.associated + unless obj.nil? + if op == 'destroy' + destroy_es_items('requests', 'destroy_doc_nested', obj.id) + else + identifier = begin self.smooch_user_external_identifier&.value rescue self.smooch_user_external_identifier end + data = { + 'username' => self.smooch_data['name'], + 'identifier' => identifier&.gsub(/[[:space:]|-]/, ''), + 'content' => self.smooch_data['text'], + 'language' => self.language, + } + options = { op: op, pm_id: obj.id, nested_key: 'requests', keys: data.keys, data: data, skip_get_data: true } + self.add_update_nested_obj(options) + end + end + end +end diff --git a/app/models/tipline_resource.rb b/app/models/tipline_resource.rb index 377e36c3c0..4bcb5ac8a1 100644 --- a/app/models/tipline_resource.rb +++ b/app/models/tipline_resource.rb @@ -10,6 +10,7 @@ class TiplineResource < ApplicationRecord validates_inclusion_of :language, in: ->(resource) { resource.team.get_languages.to_a } belongs_to :team, optional: true + has_many :tipline_requests, as: :associated def format_as_tipline_message message = [] diff --git a/app/models/user.rb b/app/models/user.rb index 64bcc7b4b3..72c130b719 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -27,6 +27,7 @@ class ToSOrPrivacyPolicyReadError < StandardError; end has_many :fact_checks has_many :feeds has_many :feed_invitations + has_many :tipline_requests devise :registerable, :recoverable, :rememberable, :trackable, :validatable, :confirmable, diff --git a/config/initializers/report_designer.rb b/config/initializers/report_designer.rb index da371ad0c4..9a19c81066 100644 --- a/config/initializers/report_designer.rb +++ b/config/initializers/report_designer.rb @@ -209,7 +209,7 @@ def copy_report_image_paths def sent_count if self.annotation_type == 'report_design' pmids = self.annotated.related_items_ids - DynamicAnnotation::Field.joins(:annotation).where(field_name: 'smooch_report_received', 'annotations.annotated_type' => 'ProjectMedia', 'annotations.annotated_id' => pmids).count + TiplineRequest.where(associated_type: 'ProjectMedia', associated_id: pmids).where.not(smooch_report_received_at: 0).count end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 200415dd83..6ed46bd9f0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -110,6 +110,7 @@ en: invalid_fact_check_language_value: Sorry, language value not supported fact_check_empty_title_and_summary: Sorry, you should fill title or summary invalid_feed_saved_search_value: should belong to a workspace that is part of this feed + platform_allowed_values_error: 'cannot be of type %{type}, allowed types: %{allowed_types}' activerecord: models: link: Link diff --git a/db/migrate/20190128175927_create_smooch_annotation_type.rb b/db/migrate/20190128175927_create_smooch_annotation_type.rb deleted file mode 100644 index 7af8e20018..0000000000 --- a/db/migrate/20190128175927_create_smooch_annotation_type.rb +++ /dev/null @@ -1,8 +0,0 @@ -class CreateSmoochAnnotationType < ActiveRecord::Migration[4.2] - require 'sample_data' - include SampleData - - def change - create_annotation_type_and_fields('Smooch', { 'Data' => ['JSON', false] }) - end -end diff --git a/db/migrate/20201016004453_add_smooch_received_field_to_smooch_annotations.rb b/db/migrate/20201016004453_add_smooch_received_field_to_smooch_annotations.rb deleted file mode 100644 index 71c2ccc436..0000000000 --- a/db/migrate/20201016004453_add_smooch_received_field_to_smooch_annotations.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'sample_data' -include SampleData -class AddSmoochReceivedFieldToSmoochAnnotations < ActiveRecord::Migration[4.2] - def change - t = DynamicAnnotation::FieldType.where(field_type: 'timestamp').last || create_field_type(field_type: 'timestamp', label: 'Timestamp') - at = DynamicAnnotation::AnnotationType.where(annotation_type: 'smooch').last - unless at.nil? - create_field_instance annotation_type_object: at, name: 'smooch_report_received', label: 'Last time the requestor received a report for this request', field_type_object: t, optional: true - end - end -end diff --git a/db/migrate/20201117131952_add_conversation_id_to_smooch.rb b/db/migrate/20201117131952_add_conversation_id_to_smooch.rb deleted file mode 100644 index abe44763af..0000000000 --- a/db/migrate/20201117131952_add_conversation_id_to_smooch.rb +++ /dev/null @@ -1,10 +0,0 @@ -class AddConversationIdToSmooch < ActiveRecord::Migration[4.2] - require 'sample_data' - include SampleData - - def change - at = DynamicAnnotation::AnnotationType.where(annotation_type: 'smooch').last - ft = DynamicAnnotation::FieldType.where(field_type: 'text').last || create_field_type(field_type: 'text', label: 'Text') - create_field_instance annotation_type_object: at, name: 'smooch_conversation_id', label: 'Conversation Id', field_type_object: ft, optional: true - end -end diff --git a/db/migrate/20201207042158_add_request_type_and_resource_to_smooch.rb b/db/migrate/20201207042158_add_request_type_and_resource_to_smooch.rb deleted file mode 100644 index b12b3e74e2..0000000000 --- a/db/migrate/20201207042158_add_request_type_and_resource_to_smooch.rb +++ /dev/null @@ -1,11 +0,0 @@ -class AddRequestTypeAndResourceToSmooch < ActiveRecord::Migration[4.2] - require 'sample_data' - include SampleData - - def change - at = DynamicAnnotation::AnnotationType.where(annotation_type: 'smooch').last - ft = DynamicAnnotation::FieldType.where(field_type: 'text').last || create_field_type(field_type: 'text', label: 'Text') - create_field_instance annotation_type_object: at, name: 'smooch_request_type', label: 'Request Type', field_type_object: ft, optional: true - create_field_instance annotation_type_object: at, name: 'smooch_resource_id', label: 'Resource Id', field_type_object: ft, optional: true - end -end diff --git a/db/migrate/20231011090947_add_smooch_sent_fields_to_smooch_annotations.rb b/db/migrate/20231011090947_add_smooch_sent_fields_to_smooch_annotations.rb deleted file mode 100644 index 51bddf7827..0000000000 --- a/db/migrate/20231011090947_add_smooch_sent_fields_to_smooch_annotations.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'sample_data' -include SampleData -class AddSmoochSentFieldsToSmoochAnnotations < ActiveRecord::Migration[6.1] - def change - t = DynamicAnnotation::FieldType.where(field_type: 'timestamp').last || create_field_type(field_type: 'timestamp', label: 'Timestamp') - at = DynamicAnnotation::AnnotationType.where(annotation_type: 'smooch').last - unless at.nil? - create_field_instance annotation_type_object: at, name: 'smooch_report_correction_sent_at', label: 'Report correction sent time', field_type_object: t, optional: true - create_field_instance annotation_type_object: at, name: 'smooch_report_sent_at', label: 'Report sent time', field_type_object: t, optional: true - end - end -end diff --git a/db/migrate/20231026162554_add_smooch_message_id_field.rb b/db/migrate/20231026162554_add_smooch_message_id_field.rb deleted file mode 100644 index 0d4fb0f6bd..0000000000 --- a/db/migrate/20231026162554_add_smooch_message_id_field.rb +++ /dev/null @@ -1,11 +0,0 @@ -class AddSmoochMessageIdField < ActiveRecord::Migration[6.1] - require 'sample_data' - include SampleData - - def change - at = DynamicAnnotation::AnnotationType.where(annotation_type: 'smooch').last - ft = DynamicAnnotation::FieldType.where(field_type: 'text').last || create_field_type(field_type: 'text', label: 'Text') - create_field_instance annotation_type_object: at, name: 'smooch_message_id', label: 'Message Id', field_type_object: ft, optional: true - execute %{CREATE UNIQUE INDEX smooch_request_message_id_unique_id ON dynamic_annotation_fields (value) WHERE field_name = 'smooch_message_id' AND value <> '' AND value <> '""'} - end -end diff --git a/db/migrate/20231122054128_create_tipline_requests.rb b/db/migrate/20231122054128_create_tipline_requests.rb new file mode 100644 index 0000000000..30f5817086 --- /dev/null +++ b/db/migrate/20231122054128_create_tipline_requests.rb @@ -0,0 +1,27 @@ +class CreateTiplineRequests < ActiveRecord::Migration[6.1] + def change + create_table :tipline_requests do |t| + t.string :language, null: false, index: true + t.string :tipline_user_uid, index: true + t.string :platform, null: false, index: true + t.string :smooch_request_type, null: false + t.string :smooch_resource_id, null: true + t.string :smooch_message_id, null: true, default: '' + t.string :smooch_conversation_id, null: true + t.jsonb :smooch_data, null: false, default: {} + t.references :associated, polymorphic: true, null: false + t.references :team, null: false + t.references :user + t.integer :smooch_report_received_at, default: 0 + t.integer :smooch_report_update_received_at, default: 0 + t.integer :smooch_report_correction_sent_at, default: 0 + t.integer :smooch_report_sent_at, default: 0 + t.timestamps + end + add_index :tipline_requests, [:associated_type, :associated_id] + add_index :tipline_requests, :smooch_message_id, unique: true, where: "smooch_message_id IS NOT NULL AND smooch_message_id != ''" + # Set start value for the ID + id = DynamicAnnotation::Field.where(field_name: 'smooch_data').last&.id || 0 + execute "SELECT setval('tipline_requests_id_seq', #{id})" if id > 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index 9839702263..bec70938bd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -714,6 +714,35 @@ t.index ["team_id"], name: "index_tipline_newsletters_on_team_id" end + create_table "tipline_requests", force: :cascade do |t| + t.string "language", null: false + t.string "tipline_user_uid" + t.string "platform", null: false + t.string "smooch_request_type", null: false + t.string "smooch_resource_id" + t.string "smooch_message_id", default: "" + t.string "smooch_conversation_id" + t.jsonb "smooch_data", default: {}, null: false + t.string "associated_type", null: false + t.bigint "associated_id", null: false + t.bigint "team_id", null: false + t.bigint "user_id" + t.integer "smooch_report_received_at", default: 0 + t.integer "smooch_report_update_received_at", default: 0 + t.integer "smooch_report_correction_sent_at", default: 0 + t.integer "smooch_report_sent_at", default: 0 + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["associated_type", "associated_id"], name: "index_tipline_requests_on_associated" + t.index ["associated_type", "associated_id"], name: "index_tipline_requests_on_associated_type_and_associated_id" + t.index ["language"], name: "index_tipline_requests_on_language" + t.index ["platform"], name: "index_tipline_requests_on_platform" + t.index ["smooch_message_id"], name: "index_tipline_requests_on_smooch_message_id", unique: true, where: "((smooch_message_id IS NOT NULL) AND ((smooch_message_id)::text <> ''::text))" + t.index ["team_id"], name: "index_tipline_requests_on_team_id" + t.index ["tipline_user_uid"], name: "index_tipline_requests_on_tipline_user_uid" + t.index ["user_id"], name: "index_tipline_requests_on_user_id" + end + create_table "tipline_resources", id: :serial, force: :cascade do |t| t.string "uuid", default: "", null: false t.string "title", default: "", null: false diff --git a/db/seeds.rb b/db/seeds.rb index f18959fdd6..cb341bc33c 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -220,13 +220,14 @@ def create_tipline_user_and_data(project_media, team) 'app_id': random_string } - fields = { + tr = TiplineRequest.create!( + associated: project_media, + team_id: project_media.team_id, smooch_request_type: ['default_requests', 'timeout_search_requests', 'relevant_search_result_requests'].sample, - smooch_data: smooch_data.to_json, - smooch_report_received: [Time.now.to_i, nil].sample - } - - Dynamic.create!(annotation_type: 'smooch', annotated: project_media, annotator: BotUser.smooch_user, set_fields: fields.to_json) + smooch_data: smooch_data, + smooch_report_received_at: [Time.now.to_i, nil].sample, + user_id: BotUser.smooch_user&.id + ) end def create_tipline_requests(team, project_medias, x_times) diff --git a/lib/check_statistics.rb b/lib/check_statistics.rb index 8b132fbc38..ac2d2a52f3 100644 --- a/lib/check_statistics.rb +++ b/lib/check_statistics.rb @@ -3,43 +3,32 @@ class WhatsAppInsightsApiError < ::StandardError; end class << self def requests(team_id, platform, start_date, end_date, language, type = nil) - relation = Annotation - .where(annotation_type: 'smooch') - .joins("INNER JOIN dynamic_annotation_fields fs ON fs.annotation_id = annotations.id AND fs.field_name = 'smooch_data'") - .where("fs.value_json->'source'->>'type' = ?", platform) - .where("fs.value_json->>'language' = ?", language) - .where('t.id' => team_id) - .where('annotations.created_at' => start_date..end_date) - unless type.nil? - relation = relation - .joins("INNER JOIN dynamic_annotation_fields fs2 ON fs2.annotation_id = annotations.id AND fs2.field_name = 'smooch_request_type'") - .where('fs2.value' => type.to_json) - end - relation + conditions = { + created_at: start_date..end_date, + team_id: team_id, + language: language, + platform: platform + } + conditions[:smooch_request_type] = type unless type.nil? + TiplineRequest.where(conditions) end def reports_received(team_id, platform, start_date, end_date, language) - DynamicAnnotation::Field - .where(field_name: 'smooch_report_received') - .joins("INNER JOIN annotations a ON a.id = dynamic_annotation_fields.annotation_id INNER JOIN project_medias pm ON pm.id = a.annotated_id AND a.annotated_type = 'ProjectMedia' INNER JOIN dynamic_annotation_fields fs ON fs.annotation_id = a.id AND fs.field_name = 'smooch_data'") - .where('pm.team_id' => team_id) - .where("fs.value_json->'source'->>'type' = ?", platform) - .where("fs.value_json->>'language' = ?", language) - .where('dynamic_annotation_fields.created_at' => start_date..end_date) + TiplineRequest.where(team_id: team_id, language: language, smooch_report_received_at: start_date.to_datetime.to_i..end_date.to_datetime.to_i, platform: platform) end def project_media_requests(team_id, platform, start_date, end_date, language, type = nil) base = requests(team_id, platform, start_date, end_date, language, type) - base.joins("INNER JOIN project_medias pm ON pm.id = annotations.annotated_id AND annotations.annotated_type = 'ProjectMedia' INNER JOIN teams t ON t.id = pm.team_id") + base.where(associated_type: 'ProjectMedia') end def team_requests(team_id, platform, start_date, end_date, language) base = requests(team_id, platform, start_date, end_date, language) - base.joins("INNER JOIN teams t ON annotations.annotated_type = 'Team' AND t.id = annotations.annotated_id") + base.where(associated_type: 'Team') end def unique_requests_count(relation) - relation.group("fs.value_json #>> '{source,originalMessageId}'").count.size + relation.group("smooch_data #>> '{source,originalMessageId}'").count.size end def number_of_newsletters_sent(team_id, start_date, end_date, language) @@ -162,12 +151,12 @@ def get_statistics(start_date, end_date, team_id, platform, language, tracing_at uids = [] CheckTracer.in_span('CheckStatistics#unique_users', attributes: tracing_attributes) do # Number of unique users - project_media_requests(team_id, platform, start_date, end_date, language).find_each do |a| - uid = begin JSON.parse(a.load.get_field_value('smooch_data'))['authorId'] rescue nil end + project_media_requests(team_id, platform, start_date, end_date, language).find_each do |tr| + uid = tr.tipline_user_uid uids << uid if !uid.nil? && !uids.include?(uid) end - team_requests(team_id, platform, start_date, end_date, language).find_each do |a| - uid = begin JSON.parse(a.load.get_field_value('smooch_data'))['authorId'] rescue nil end + team_requests(team_id, platform, start_date, end_date, language).find_each do |tr| + uid = tr.tipline_user_uid uids << uid if !uid.nil? && !uids.include?(uid) end statistics[:unique_users] = uids.size @@ -175,7 +164,8 @@ def get_statistics(start_date, end_date, team_id, platform, language, tracing_at CheckTracer.in_span('CheckStatistics#returning_users', attributes: tracing_attributes) do # Number of returning users (at least one session in the current month, and at least one session in the last previous 2 months) - statistics[:returning_users] = DynamicAnnotation::Field.where(field_name: 'smooch_data', created_at: start_date.ago(2.months)..start_date).where("value_json->>'authorId' IN (?) AND value_json->>'language' = ?", uids, language).collect{ |f| f.value_json['authorId'] }.uniq.size + statistics[:returning_users] = TiplineRequest.where(created_at: start_date.ago(2.months)..start_date) + .where(tipline_user_uid: uids, language: language).map(&:tipline_user_uid).uniq.size end CheckTracer.in_span('CheckStatistics#reports_sent_to_users', attributes: tracing_attributes) do @@ -185,17 +175,16 @@ def get_statistics(start_date, end_date, team_id, platform, language, tracing_at CheckTracer.in_span('CheckStatistics#unique_users_who_received_report', attributes: tracing_attributes) do # Number of unique users who received a report - statistics[:unique_users_who_received_report] = [reports_received(team_id, platform, start_date, end_date, language) + project_media_requests(team_id, platform, start_date, end_date, language, 'relevant_search_result_requests')].flatten.collect do |f| - annotation = f.is_a?(Annotation) ? f : f.annotation - JSON.parse(annotation.load.get_field_value('smooch_data'))['authorId'] + statistics[:unique_users_who_received_report] = [reports_received(team_id, platform, start_date, end_date, language) + project_media_requests(team_id, platform, start_date, end_date, language, 'relevant_search_result_requests')].flatten.collect do |tr| + tr.tipline_user_uid end.uniq.size end CheckTracer.in_span('CheckStatistics#median_response_time', attributes: tracing_attributes) do # Average time to publishing times = [] - reports_received(team_id, platform, start_date, end_date, language).find_each do |f| - times << (f.created_at - f.annotation.created_at) + reports_received(team_id, platform, start_date, end_date, language).find_each do |tr| + times << (tr.smooch_report_received_at - tr.created_at.to_i) end median_response_time_in_seconds = times.size == 0 ? nil : times.sum.to_f / times.size statistics[:median_response_time] = median_response_time_in_seconds diff --git a/lib/relay.idl b/lib/relay.idl index bc2ab83e63..7d3b1abcff 100644 --- a/lib/relay.idl +++ b/lib/relay.idl @@ -1481,42 +1481,6 @@ type CreateDynamicAnnotationSlackMessagePayload { versionEdge: VersionEdge } -""" -Autogenerated input type of CreateDynamicAnnotationSmooch -""" -input CreateDynamicAnnotationSmoochInput { - action: String - action_data: String - annotated_id: String - annotated_type: String - - """ - A unique identifier for the client performing the mutation. - """ - clientMutationId: String - fragment: String - set_attribution: String - set_fields: String! -} - -""" -Autogenerated return type of CreateDynamicAnnotationSmooch -""" -type CreateDynamicAnnotationSmoochPayload { - """ - A unique identifier for the client performing the mutation. - """ - clientMutationId: String - dynamic: Dynamic - dynamicEdge: DynamicEdge - dynamic_annotation_smooch: Dynamic_annotation_smooch - dynamic_annotation_smoochEdge: Dynamic_annotation_smoochEdge - project: Project - project_media: ProjectMedia - source: Source - versionEdge: VersionEdge -} - """ Autogenerated input type of CreateDynamicAnnotationSmoochResponse """ @@ -3284,31 +3248,6 @@ type DestroyDynamicAnnotationSlackMessagePayload { source: Source } -""" -Autogenerated input type of DestroyDynamicAnnotationSmooch -""" -input DestroyDynamicAnnotationSmoochInput { - """ - A unique identifier for the client performing the mutation. - """ - clientMutationId: String - id: ID -} - -""" -Autogenerated return type of DestroyDynamicAnnotationSmooch -""" -type DestroyDynamicAnnotationSmoochPayload { - """ - A unique identifier for the client performing the mutation. - """ - clientMutationId: String - deletedId: ID - project: Project - project_media: ProjectMedia - source: Source -} - """ Autogenerated input type of DestroyDynamicAnnotationSmoochResponse """ @@ -5894,106 +5833,6 @@ type Dynamic_annotation_slack_messageEdge { node: Dynamic_annotation_slack_message } -type Dynamic_annotation_smooch implements Node { - annotated_id: String - annotated_type: String - annotation_type: String - annotations( - """ - Returns the elements in the list that come after the specified cursor. - """ - after: String - annotation_type: 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 - ): AnnotationUnionConnection - annotator: Annotator - assignments( - """ - 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 - ): UserConnection - content: String - created_at: String - data: JsonStringType - dbid: String - file_data: JsonStringType - id: ID! - lock_version: Int - locked: Boolean - medias( - """ - 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 - ): ProjectMediaConnection - parsed_fragment: JsonStringType - permissions: String - project: Project - team: Team - updated_at: String - version: Version -} - -""" -An edge in a connection. -""" -type Dynamic_annotation_smoochEdge { - """ - A cursor for use in pagination. - """ - cursor: String! - - """ - The item at the end of the edge. - """ - node: Dynamic_annotation_smooch -} - type Dynamic_annotation_smooch_response implements Node { annotated_id: String annotated_type: String @@ -8711,12 +8550,6 @@ type MutationType { """ input: CreateDynamicAnnotationSlackMessageInput! ): CreateDynamicAnnotationSlackMessagePayload - createDynamicAnnotationSmooch( - """ - Parameters for CreateDynamicAnnotationSmooch - """ - input: CreateDynamicAnnotationSmoochInput! - ): CreateDynamicAnnotationSmoochPayload createDynamicAnnotationSmoochResponse( """ Parameters for CreateDynamicAnnotationSmoochResponse @@ -9059,12 +8892,6 @@ type MutationType { """ input: DestroyDynamicAnnotationSlackMessageInput! ): DestroyDynamicAnnotationSlackMessagePayload - destroyDynamicAnnotationSmooch( - """ - Parameters for DestroyDynamicAnnotationSmooch - """ - input: DestroyDynamicAnnotationSmoochInput! - ): DestroyDynamicAnnotationSmoochPayload destroyDynamicAnnotationSmoochResponse( """ Parameters for DestroyDynamicAnnotationSmoochResponse @@ -9497,12 +9324,6 @@ type MutationType { """ input: UpdateDynamicAnnotationSlackMessageInput! ): UpdateDynamicAnnotationSlackMessagePayload - updateDynamicAnnotationSmooch( - """ - Parameters for UpdateDynamicAnnotationSmooch - """ - input: UpdateDynamicAnnotationSmoochInput! - ): UpdateDynamicAnnotationSmoochPayload updateDynamicAnnotationSmoochResponse( """ Parameters for UpdateDynamicAnnotationSmoochResponse @@ -10122,7 +9943,6 @@ type ProjectMedia implements Node { dynamic_annotation_report_design: Dynamic dynamic_annotation_reverse_image: Dynamic dynamic_annotation_slack_message: Dynamic - dynamic_annotation_smooch: Dynamic dynamic_annotation_smooch_response: Dynamic dynamic_annotation_smooch_user: Dynamic dynamic_annotation_syrian_archive_data: Dynamic @@ -10458,27 +10278,6 @@ type ProjectMedia implements Node { """ last: Int ): DynamicConnection - dynamic_annotations_smooch( - """ - 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 - ): DynamicConnection dynamic_annotations_smooch_response( """ Returns the elements in the list that come after the specified cursor. @@ -13121,13 +12920,14 @@ type TiplineNewsletterEdge { TiplineRequest type """ type TiplineRequest implements Node { - annotation: Annotation - annotation_id: Int associated_graphql_id: String + associated_id: Int + associated_type: String created_at: String dbid: Int id: ID! permissions: String + smooch_data: JsonStringType smooch_report_correction_sent_at: Int smooch_report_received_at: Int smooch_report_sent_at: Int @@ -13137,7 +12937,6 @@ type TiplineRequest implements Node { smooch_user_request_language: String smooch_user_slack_channel_url: String updated_at: String - value_json: JsonStringType } """ @@ -13944,46 +13743,6 @@ type UpdateDynamicAnnotationSlackMessagePayload { versionEdge: VersionEdge } -""" -Autogenerated input type of UpdateDynamicAnnotationSmooch -""" -input UpdateDynamicAnnotationSmoochInput { - action: String - action_data: String - annotated_id: String - annotated_type: String - assigned_to_ids: String - - """ - A unique identifier for the client performing the mutation. - """ - clientMutationId: String - fragment: String - id: ID - lock_version: Int - locked: Boolean - set_attribution: String - set_fields: String -} - -""" -Autogenerated return type of UpdateDynamicAnnotationSmooch -""" -type UpdateDynamicAnnotationSmoochPayload { - """ - A unique identifier for the client performing the mutation. - """ - clientMutationId: String - dynamic: Dynamic - dynamicEdge: DynamicEdge - dynamic_annotation_smooch: Dynamic_annotation_smooch - dynamic_annotation_smoochEdge: Dynamic_annotation_smoochEdge - project: Project - project_media: ProjectMedia - source: Source - versionEdge: VersionEdge -} - """ Autogenerated input type of UpdateDynamicAnnotationSmoochResponse """ diff --git a/lib/sample_data.rb b/lib/sample_data.rb index bc7da857c8..913abd8226 100644 --- a/lib/sample_data.rb +++ b/lib/sample_data.rb @@ -845,6 +845,21 @@ def create_tipline_subscription(options = {}) }.merge(options)) end + def create_tipline_request(options = {}) + tr = TiplineRequest.new + tr.smooch_data = { language: 'en', authorId: random_string, source: { type: 'whatsapp' } } unless options.has_key?(:smooch_data) + tr.team_id = options[:team_id] || create_team.id unless options.has_key?(:team_id) + tr.associated = options[:associated] || create_project_media + tr.smooch_request_type = 'default_requests' unless options.has_key?(:smooch_request_type) + tr.platform = 'whatsapp' unless options.has_key?(:platform) + tr.language = 'en' unless options.has_key?(:language) + options.each do |key, value| + tr.send("#{key}=", value) if tr.respond_to?("#{key}=") + end + tr.save! + tr.reload + end + def create_cluster(options = {}) options[:project_media] = create_project_media unless options.has_key?(:project_media) Cluster.create!(options) diff --git a/lib/tasks/migrate/20231122054128_migrate_tipline_requests.rake b/lib/tasks/migrate/20231122054128_migrate_tipline_requests.rake new file mode 100644 index 0000000000..16178032d3 --- /dev/null +++ b/lib/tasks/migrate/20231122054128_migrate_tipline_requests.rake @@ -0,0 +1,347 @@ +namespace :check do + namespace :migrate do + def parse_args(args) + output = {} + return output if args.blank? + args.each do |a| + arg = a.split('&') + arg.each do |pair| + key, value = pair.split(':') + output.merge!({ key => value }) + end + end + output + end + + def migrate_team_tipline_requests(team, batch_size) + total_count = Annotation.where(annotation_type: 'smooch', annotated_type: 'Team', annotated_id: team.id).count + failed_teams = [] + if total_count > 0 + puts "\nMigrating Team requests[#{team.slug}]: #{total_count} requests" + inserts = 0 + Annotation.where(annotation_type: 'smooch', annotated_type: 'Team', annotated_id: team.id) + .find_in_batches(:batch_size => batch_size) do |annotations| + print '.' + smooch_obj = {} + smooch_user = {} + obj_requests = Hash.new {|hash, key| hash[key] = [] } + # Collect request associated id and user id + annotations.each do |d| + smooch_obj[d.id] = d.annotated_id + smooch_user[d.id] = d.annotator_id + end + DynamicAnnotation::Field.where(annotation_type: 'smooch', annotation_id: smooch_obj.keys).find_each do |f| + print '.' + value = f.value + # I mapped `smooch_report_received` field in two columns `smooch_report_received_at` & `smooch_report_update_received_at` + field_name = f.field_name == 'smooch_report_received' ? 'smooch_report_received_at' : f.field_name + if field_name == 'smooch_data' + value.gsub!('\u0000', '') if value.is_a?(String) # Avoid PG::UntranslatableCharacter exception + value = begin JSON.parse(value) rescue {} end + # These fields are indifferent TiplineRequest columns so collect these fields with their values + # N.B: I set smooch_data.id as a primary key for TiplineRequest table + # and set a default values for some columns to avoid PG error + sd_fields = [ + { 'id' => f.id }, + { 'tipline_user_uid' => value.dig('authorId') }, + { 'language' => value.dig('language') || 'en' }, + { 'platform' => value.dig('source', 'type') || 'whatsapp' }, + { 'created_at' => f.created_at }, + { 'updated_at' => f.updated_at }, + ] + obj_requests[f.annotation_id].concat(sd_fields) + end + obj_requests[f.annotation_id] << { field_name => value } + if field_name == 'smooch_report_received_at' && f.created_at != f.updated_at + # Get the value for `smooch_report_update_received_at` column + obj_requests[f.annotation_id] << { 'smooch_report_update_received_at' => value } + end + end + requests = [] + obj_requests.each do |d_id, fields| + # Build TiplineRequest raw and should include all existing columns + r = { + associated_type: 'Team', + associated_id: smooch_obj[d_id], + user_id: smooch_user[d_id], + team_id: team.id, + smooch_request_type: 'default_requests', + smooch_resource_id: nil, + smooch_message_id: '', + smooch_conversation_id: nil, + smooch_report_received_at: 0, + smooch_report_update_received_at: 0, + smooch_report_correction_sent_at: 0, + smooch_report_sent_at: 0, + }.with_indifferent_access + fields.each do |raws| + raws.each{|k, v| r[k] = v } + end + requests << r + end + unless requests.blank? + inserts += requests.count + puts "\nImporting Team requests[#{team.slug}]: #{inserts}/#{total_count}\n" + begin + TiplineRequest.insert_all(requests) + rescue + failed_teams << team.id unless failed_teams.include?(team.id) + end + end + end + end + failed_teams + end + + def bulk_import_requests_items(annotated_type, ids, team_id) + print '.' + smooch_obj = {} + smooch_user = {} + obj_requests = Hash.new {|hash, key| hash[key] = [] } + # Collect request associated id and user id + Annotation.where(annotation_type: 'smooch', annotated_type: annotated_type, annotated_id: ids).find_each do |d| + print '.' + smooch_obj[d.id] = d.annotated_id + smooch_user[d.id] = d.annotator_id + end + DynamicAnnotation::Field.where(annotation_type: 'smooch', annotation_id: smooch_obj.keys).find_each do |f| + print '.' + value = f.value + # I mapped `smooch_report_received` field in two columns `smooch_report_received_at` & `smooch_report_update_received_at` + field_name = f.field_name == 'smooch_report_received' ? 'smooch_report_received_at' : f.field_name + if field_name == 'smooch_data' + value.gsub!('\u0000', '') if value.is_a?(String) # Avoid PG::UntranslatableCharacter exception + value = begin JSON.parse(value) rescue {} end + # These fields are indifferent TiplineRequest columns so collect these fields with their values + # N.B: I set smooch_data.id as a primary key for TiplineRequest table + # and set a default values for some columns to avoid PG error + sd_fields = [ + { 'id' => f.id }, + { 'tipline_user_uid' => value.dig('authorId') }, + { 'language' => value.dig('language') || 'en' }, + { 'platform' => value.dig('source', 'type') || 'whatsapp' }, + { 'created_at' => f.created_at }, + { 'updated_at' => f.updated_at }, + ] + obj_requests[f.annotation_id].concat(sd_fields) + end + obj_requests[f.annotation_id] << { field_name => value } + if field_name == 'smooch_report_received_at' && f.created_at != f.updated_at + # Get the value for `smooch_report_update_received_at` column + obj_requests[f.annotation_id] << { 'smooch_report_update_received_at' => value } + end + end + requests = [] + obj_requests.each do |d_id, fields| + # Build TiplineRequest raw and should include all existing columns + r = { + associated_type: annotated_type, + associated_id: smooch_obj[d_id], + user_id: smooch_user[d_id], + team_id: team_id, + smooch_request_type: 'default_requests', + smooch_resource_id: nil, + smooch_message_id: '', + smooch_conversation_id: nil, + smooch_report_received_at: 0, + smooch_report_update_received_at: 0, + smooch_report_correction_sent_at: 0, + smooch_report_sent_at: 0, + }.with_indifferent_access + fields.each do |raws| + raws.each{|k, v| r[k] = v } + end + requests << r + end + requests + end + # Migrate TiplineRequests + # bundle exec rails check:migrate:migrate_tipline_requests['slug:team_slug&batch_size:batch_size'] + task migrate_tipline_requests: :environment do |_t, args| + started = Time.now.to_i + data_args = parse_args args.extras + batch_size = data_args['batch_size'] || 1500 + batch_size = batch_size.to_i + slug = data_args['slug'] + condition = {} + last_team_id = Rails.cache.read('check:migrate:migrate_tipline_requests:team_id') || 0 + unless slug.blank? + last_team_id = 0 + condition = { slug: slug } + end + failed_project_media_requests = [] + failed_team_requests = [] + failed_tipline_resource_requests = [] + Team.where(condition).where('id > ?', last_team_id).find_each do |team| + print '.' + migrate_teams = Rails.cache.read('check:migrate:migrate_tipline_requests:migrate_teams') || [] + next if migrate_teams.include?(team.id) + # Migrated Team requests + failed_team_requests = migrate_team_tipline_requests(team, batch_size) + # Migrate TiplineResource requests + total_count = team.tipline_resources.joins("INNER JOIN annotations a ON a.annotated_id = tipline_resources.id") + .where("a.annotated_type = ? AND a.annotation_type = ?", 'TiplineResource', 'smooch').count + if total_count > 0 + puts "\nMigrating TiplineResource requests[#{team.slug}]: #{total_count} requests" + inserts = 0 + team.tipline_resources.find_in_batches(:batch_size => batch_size) do |items| + print '.' + ids = items.map(&:id) + requests = bulk_import_requests_items('TiplineResource', ids, team.id) + unless requests.blank? + inserts += requests.count + puts "\nImporting TiplineResource[#{team.slug}]: #{inserts}/#{total_count}\n" + begin + TiplineRequest.insert_all(requests) + rescue + failed_tipline_resource_requests << team.id unless failed_tipline_resource_requests.include?(team.id) + end + end + end + end + # Migrate ProjectMedia requests + # Get the total count for team requests + total_count = team.project_medias.joins("INNER JOIN annotations a ON a.annotated_id = project_medias.id") + .where("a.annotated_type = ? AND a.annotation_type = ?", 'ProjectMedia', 'smooch').count + if total_count > 0 + puts "\nMigrating ProjectMedia requests[#{team.slug}]: #{total_count} requests" + inserts = 0 + team.project_medias.find_in_batches(:batch_size => batch_size) do |pms| + print '.' + ids = pms.map(&:id) + requests = bulk_import_requests_items('ProjectMedia', ids, team.id) + unless requests.blank? + inserts += requests.count + puts "\nImporting ProjectMedia requests[#{team.slug}]: #{inserts}/#{total_count}\n" + begin + TiplineRequest.insert_all(requests) + rescue + failed_teams << team.id unless failed_teams.include?(team.id) + end + end + end + end + unless slug.blank? + migrate_teams << team.id + Rails.cache.write('check:migrate:migrate_tipline_requests:migrate_teams', migrate_teams) + end + Rails.cache.write('check:migrate:migrate_tipline_requests:team_id', team.id) if slug.blank? + end + puts "Failed to import some project media requests related to the following teams #{failed_project_media_requests.inspect}" if failed_project_media_requests.length > 0 + puts "Failed to import some team requests related to the following teams #{failed_team_requests.inspect}" if failed_team_requests.length > 0 + puts "Failed to import some tipline resource requests related to the following teams #{failed_tipline_resource_requests.inspect}" if failed_tipline_resource_requests.length > 0 + minutes = ((Time.now.to_i - started) / 60).to_i + puts "[#{Time.now}] Done in #{minutes} minutes." + end + + # list teams that have a different count between TiplineRequest and smooch annotation (list teams that not fully migrated) + # bundle exec rails check:migrate:migrate_tipline_requests_status[team_slug1, team_slug2, ...] + task migrate_tipline_requests_status: :environment do |_t, args| + # Get missing requests based on a comparison between TiplineRequest.id and smooch_data field id + slugs = args.extras + condition = {} + condition = { slug: slugs } unless slugs.blank? + logs = [] + Team.where(condition).find_each do |team| + print '.' + requests_ids = TiplineRequest.where(team_id: team.id).map(&:id) + requests_count = requests_ids.count + smooch_ids = Annotation.where(annotation_type: 'smooch', annotated_type: 'ProjectMedia') + .joins("INNER JOIN project_medias pm ON pm.id = annotations.annotated_id") + .where('pm.team_id = ?', team.id) + sd_ids = DynamicAnnotation::Field.where(field_name: 'smooch_data', annotation_type: 'smooch', annotation_id: smooch_ids) + .where.not(id: requests_ids).map(&:id) + logs << {id: team.id, slug: team.slug, requests: requests_count, smooch: sd_ids} if sd_ids.length > 0 + end + puts "List of teams that not fully migrated" + pp logs + end + + # Migrate missing requests related to specific teams + # bundle exec rails check:migrate:migrate_tipline_requests_missing_requests['slug:team_slug&batch_size:batch_size'] + task migrate_tipline_requests_missing_requests: :environment do |_t, args| + data_args = parse_args args.extras + slug = data_args['slug'] + raise "You must call rake task with team slugs" if slug.blank? + condition = { slug: slug } + batch_size = data_args['batch_size'] || 1000 + started = Time.now.to_i + Team.where(condition).find_each do |team| + print '.' + requests_ids = TiplineRequest.where(team_id: team.id).map(&:id) + smooch_ids = Annotation.where(annotation_type: 'smooch', annotated_type: 'ProjectMedia') + .joins("INNER JOIN project_medias pm ON pm.id = annotations.annotated_id") + .where('pm.team_id = ?', team.id).map(&:id) + sd_fields = DynamicAnnotation::Field.where(field_name: 'smooch_data', annotation_type: 'smooch', annotation_id: smooch_ids) + .where.not(id: requests_ids) + sd_ids = sd_fields.map(&:id) + if sd_ids.length > 0 + total_count = sd_ids.length + inserts = 0 + print '.' + smooch_ids = sd_fields.map(&:annotation_id) + Annotation.where(id: smooch_ids).find_in_batches(:batch_size => batch_size) do |annotations| + smooch_pm = {} + smooch_user = {} + pm_requests = Hash.new {|hash, key| hash[key] = [] } + annotations.each do |d| + print '.' + smooch_pm[d.id] = d.annotated_id + smooch_user[d.id] = d.annotator_id + end + DynamicAnnotation::Field.where(annotation_type: 'smooch', annotation_id: annotations.map(&:id)).find_each do |f| + print '.' + value = f.value + field_name = f.field_name == 'smooch_report_received' ? 'smooch_report_received_at' : f.field_name + if field_name == 'smooch_data' + value.gsub!('\u0000', '') if value.is_a?(String) # Avoid PG::UntranslatableCharacter exception + value = begin JSON.parse(value) rescue {} end + sd_fields = [ + { 'id' => f.id }, + { 'tipline_user_uid' => value.dig('authorId') }, + { 'language' => value.dig('language') || 'en' }, + { 'platform' => value.dig('source', 'type') || 'whatsapp' }, + { 'created_at' => f.created_at }, + { 'updated_at' => f.updated_at }, + ] + pm_requests[f.annotation_id].concat(sd_fields) + end + pm_requests[f.annotation_id] << { field_name => value } + if field_name == 'smooch_report_received_at' && f.created_at != f.updated_at + pm_requests[f.annotation_id] << { 'smooch_report_update_received_at' => value } + end + end + requests = [] + pm_requests.each do |d_id, fields| + r = { + associated_type: 'ProjectMedia', + associated_id: smooch_pm[d_id], + user_id: smooch_user[d_id], + team_id: team.id, + smooch_request_type: 'default_requests', + smooch_resource_id: nil, + smooch_message_id: '', + smooch_conversation_id: nil, + smooch_report_received_at: 0, + smooch_report_update_received_at: 0, + smooch_report_correction_sent_at: 0, + smooch_report_sent_at: 0, + }.with_indifferent_access + fields.each do |raws| + raws.each{|k, v| r[k] = v } + end + requests << r + end + unless requests.blank? + inserts += requests.count + puts "\n#{team.slug}:: Importing #{inserts}/#{total_count} requests\n" + TiplineRequest.insert_all(requests) unless requests.blank? + end + end + end + end + minutes = ((Time.now.to_i - started) / 60).to_i + puts "[#{Time.now}] Done in #{minutes} minutes." + end + end +end diff --git a/public/relay.json b/public/relay.json index 57418f13c8..cdd95d9d4a 100644 --- a/public/relay.json +++ b/public/relay.json @@ -8520,256 +8520,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "INPUT_OBJECT", - "name": "CreateDynamicAnnotationSmoochInput", - "description": "Autogenerated input type of CreateDynamicAnnotationSmooch", - "fields": null, - "inputFields": [ - { - "name": "fragment", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "annotated_id", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "annotated_type", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "action", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "set_attribution", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "action_data", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "set_fields", - "description": null, - "type": { - "kind": "NON_NULL", - "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": "CreateDynamicAnnotationSmoochPayload", - "description": "Autogenerated return type of CreateDynamicAnnotationSmooch", - "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": "dynamic_annotation_smooch", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "Dynamic_annotation_smooch", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "dynamic_annotation_smoochEdge", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "Dynamic_annotation_smoochEdge", - "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": "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": "CreateDynamicAnnotationSmoochResponseInput", @@ -19563,8 +19313,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationSmoochInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationSmooch", + "name": "DestroyDynamicAnnotationSmoochResponseInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationSmoochResponse", "fields": null, "inputFields": [ { @@ -19598,8 +19348,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationSmoochPayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationSmooch", + "name": "DestroyDynamicAnnotationSmoochResponsePayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationSmoochResponse", "fields": [ { "name": "clientMutationId", @@ -19681,8 +19431,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationSmoochResponseInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationSmoochResponse", + "name": "DestroyDynamicAnnotationSmoochUserInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationSmoochUser", "fields": null, "inputFields": [ { @@ -19716,8 +19466,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationSmoochResponsePayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationSmoochResponse", + "name": "DestroyDynamicAnnotationSmoochUserPayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationSmoochUser", "fields": [ { "name": "clientMutationId", @@ -19799,8 +19549,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationSmoochUserInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationSmoochUser", + "name": "DestroyDynamicAnnotationSyrianArchiveDataInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationSyrianArchiveData", "fields": null, "inputFields": [ { @@ -19834,8 +19584,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationSmoochUserPayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationSmoochUser", + "name": "DestroyDynamicAnnotationSyrianArchiveDataPayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationSyrianArchiveData", "fields": [ { "name": "clientMutationId", @@ -19917,8 +19667,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationSyrianArchiveDataInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationSyrianArchiveData", + "name": "DestroyDynamicAnnotationTaskResponseDatetimeInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseDatetime", "fields": null, "inputFields": [ { @@ -19952,8 +19702,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationSyrianArchiveDataPayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationSyrianArchiveData", + "name": "DestroyDynamicAnnotationTaskResponseDatetimePayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseDatetime", "fields": [ { "name": "clientMutationId", @@ -20035,8 +19785,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseDatetimeInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseDatetime", + "name": "DestroyDynamicAnnotationTaskResponseFileUploadInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseFileUpload", "fields": null, "inputFields": [ { @@ -20070,8 +19820,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseDatetimePayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseDatetime", + "name": "DestroyDynamicAnnotationTaskResponseFileUploadPayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseFileUpload", "fields": [ { "name": "clientMutationId", @@ -20153,8 +19903,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseFileUploadInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseFileUpload", + "name": "DestroyDynamicAnnotationTaskResponseFreeTextInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseFreeText", "fields": null, "inputFields": [ { @@ -20188,8 +19938,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseFileUploadPayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseFileUpload", + "name": "DestroyDynamicAnnotationTaskResponseFreeTextPayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseFreeText", "fields": [ { "name": "clientMutationId", @@ -20271,8 +20021,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseFreeTextInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseFreeText", + "name": "DestroyDynamicAnnotationTaskResponseGeolocationInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseGeolocation", "fields": null, "inputFields": [ { @@ -20306,8 +20056,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseFreeTextPayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseFreeText", + "name": "DestroyDynamicAnnotationTaskResponseGeolocationPayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseGeolocation", "fields": [ { "name": "clientMutationId", @@ -20389,8 +20139,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseGeolocationInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseGeolocation", + "name": "DestroyDynamicAnnotationTaskResponseMultipleChoiceInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseMultipleChoice", "fields": null, "inputFields": [ { @@ -20424,8 +20174,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseGeolocationPayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseGeolocation", + "name": "DestroyDynamicAnnotationTaskResponseMultipleChoicePayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseMultipleChoice", "fields": [ { "name": "clientMutationId", @@ -20507,8 +20257,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseMultipleChoiceInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseMultipleChoice", + "name": "DestroyDynamicAnnotationTaskResponseNumberInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseNumber", "fields": null, "inputFields": [ { @@ -20542,8 +20292,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseMultipleChoicePayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseMultipleChoice", + "name": "DestroyDynamicAnnotationTaskResponseNumberPayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseNumber", "fields": [ { "name": "clientMutationId", @@ -20625,8 +20375,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseNumberInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseNumber", + "name": "DestroyDynamicAnnotationTaskResponseSingleChoiceInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseSingleChoice", "fields": null, "inputFields": [ { @@ -20660,8 +20410,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseNumberPayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseNumber", + "name": "DestroyDynamicAnnotationTaskResponseSingleChoicePayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseSingleChoice", "fields": [ { "name": "clientMutationId", @@ -20743,8 +20493,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseSingleChoiceInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseSingleChoice", + "name": "DestroyDynamicAnnotationTaskResponseUrlInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseUrl", "fields": null, "inputFields": [ { @@ -20778,8 +20528,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseSingleChoicePayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseSingleChoice", + "name": "DestroyDynamicAnnotationTaskResponseUrlPayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseUrl", "fields": [ { "name": "clientMutationId", @@ -20861,8 +20611,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseUrlInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseUrl", + "name": "DestroyDynamicAnnotationTaskResponseYesNoInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseYesNo", "fields": null, "inputFields": [ { @@ -20896,8 +20646,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseUrlPayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseUrl", + "name": "DestroyDynamicAnnotationTaskResponseYesNoPayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseYesNo", "fields": [ { "name": "clientMutationId", @@ -20979,8 +20729,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseYesNoInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationTaskResponseYesNo", + "name": "DestroyDynamicAnnotationTaskStatusInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationTaskStatus", "fields": null, "inputFields": [ { @@ -21014,8 +20764,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationTaskResponseYesNoPayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationTaskResponseYesNo", + "name": "DestroyDynamicAnnotationTaskStatusPayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationTaskStatus", "fields": [ { "name": "clientMutationId", @@ -21097,8 +20847,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationTaskStatusInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationTaskStatus", + "name": "DestroyDynamicAnnotationTeamBotResponseInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationTeamBotResponse", "fields": null, "inputFields": [ { @@ -21132,8 +20882,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationTaskStatusPayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationTaskStatus", + "name": "DestroyDynamicAnnotationTeamBotResponsePayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationTeamBotResponse", "fields": [ { "name": "clientMutationId", @@ -21215,8 +20965,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationTeamBotResponseInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationTeamBotResponse", + "name": "DestroyDynamicAnnotationTranscriptInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationTranscript", "fields": null, "inputFields": [ { @@ -21250,8 +21000,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationTeamBotResponsePayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationTeamBotResponse", + "name": "DestroyDynamicAnnotationTranscriptPayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationTranscript", "fields": [ { "name": "clientMutationId", @@ -21333,8 +21083,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationTranscriptInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationTranscript", + "name": "DestroyDynamicAnnotationTranscriptionInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationTranscription", "fields": null, "inputFields": [ { @@ -21368,8 +21118,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationTranscriptPayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationTranscript", + "name": "DestroyDynamicAnnotationTranscriptionPayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationTranscription", "fields": [ { "name": "clientMutationId", @@ -21451,8 +21201,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationTranscriptionInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationTranscription", + "name": "DestroyDynamicAnnotationTranslationInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationTranslation", "fields": null, "inputFields": [ { @@ -21486,8 +21236,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationTranscriptionPayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationTranscription", + "name": "DestroyDynamicAnnotationTranslationPayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationTranslation", "fields": [ { "name": "clientMutationId", @@ -21569,8 +21319,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationTranslationInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationTranslation", + "name": "DestroyDynamicAnnotationTranslationRequestInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationTranslationRequest", "fields": null, "inputFields": [ { @@ -21604,8 +21354,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationTranslationPayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationTranslation", + "name": "DestroyDynamicAnnotationTranslationRequestPayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationTranslationRequest", "fields": [ { "name": "clientMutationId", @@ -21687,8 +21437,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationTranslationRequestInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationTranslationRequest", + "name": "DestroyDynamicAnnotationTranslationStatusInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationTranslationStatus", "fields": null, "inputFields": [ { @@ -21722,8 +21472,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationTranslationRequestPayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationTranslationRequest", + "name": "DestroyDynamicAnnotationTranslationStatusPayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationTranslationStatus", "fields": [ { "name": "clientMutationId", @@ -21805,8 +21555,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationTranslationStatusInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationTranslationStatus", + "name": "DestroyDynamicAnnotationVerificationStatusInput", + "description": "Autogenerated input type of DestroyDynamicAnnotationVerificationStatus", "fields": null, "inputFields": [ { @@ -21840,8 +21590,8 @@ }, { "kind": "OBJECT", - "name": "DestroyDynamicAnnotationTranslationStatusPayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationTranslationStatus", + "name": "DestroyDynamicAnnotationVerificationStatusPayload", + "description": "Autogenerated return type of DestroyDynamicAnnotationVerificationStatus", "fields": [ { "name": "clientMutationId", @@ -21923,272 +21673,154 @@ }, { "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationVerificationStatusInput", - "description": "Autogenerated input type of DestroyDynamicAnnotationVerificationStatus", - "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": "DestroyDynamicAnnotationVerificationStatusPayload", - "description": "Autogenerated return type of DestroyDynamicAnnotationVerificationStatus", - "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": "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 - } - ], - "inputFields": null, - "interfaces": [ - - ], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "DestroyDynamicInput", - "description": "Autogenerated input type of DestroyDynamic", - "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": "DestroyDynamicPayload", - "description": "Autogenerated return type of DestroyDynamic", - "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": "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 - } - ], - "inputFields": null, - "interfaces": [ - - ], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "DestroyFactCheckInput", - "description": "Autogenerated input type of DestroyFactCheck", + "name": "DestroyDynamicInput", + "description": "Autogenerated input type of DestroyDynamic", + "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": "DestroyDynamicPayload", + "description": "Autogenerated return type of DestroyDynamic", + "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": "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 + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "DestroyFactCheckInput", + "description": "Autogenerated input type of DestroyFactCheck", "fields": null, "inputFields": [ { @@ -32587,523 +32219,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "OBJECT", - "name": "Dynamic_annotation_smooch", - "description": null, - "fields": [ - { - "name": "annotated_id", - "description": null, - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "annotated_type", - "description": null, - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "annotation_type", - "description": null, - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "annotations", - "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 - }, - { - "name": "annotation_type", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - } - ], - "type": { - "kind": "OBJECT", - "name": "AnnotationUnionConnection", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "annotator", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "Annotator", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "assignments", - "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": "UserConnection", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "content", - "description": null, - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "created_at", - "description": null, - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "data", - "description": null, - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "JsonStringType", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "dbid", - "description": null, - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "file_data", - "description": null, - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "JsonStringType", - "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": "lock_version", - "description": null, - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "locked", - "description": null, - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "medias", - "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": "ProjectMediaConnection", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "parsed_fragment", - "description": null, - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "JsonStringType", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "permissions", - "description": null, - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "project", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "Project", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "team", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "Team", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "updated_at", - "description": null, - "args": [ - - ], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "version", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "Version", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [ - { - "kind": "INTERFACE", - "name": "Node", - "ofType": null - } - ], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "Dynamic_annotation_smoochEdge", - "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": "Dynamic_annotation_smooch", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [ - - ], - "enumValues": null, - "possibleTypes": null - }, { "kind": "OBJECT", "name": "Dynamic_annotation_smooch_response", @@ -47252,35 +46367,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "createDynamicAnnotationSmooch", - "description": null, - "args": [ - { - "name": "input", - "description": "Parameters for CreateDynamicAnnotationSmooch", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "CreateDynamicAnnotationSmoochInput", - "ofType": null - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - } - ], - "type": { - "kind": "OBJECT", - "name": "CreateDynamicAnnotationSmoochPayload", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "createDynamicAnnotationSmoochResponse", "description": null, @@ -48934,35 +48020,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "destroyDynamicAnnotationSmooch", - "description": null, - "args": [ - { - "name": "input", - "description": "Parameters for DestroyDynamicAnnotationSmooch", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "DestroyDynamicAnnotationSmoochInput", - "ofType": null - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - } - ], - "type": { - "kind": "OBJECT", - "name": "DestroyDynamicAnnotationSmoochPayload", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "destroyDynamicAnnotationSmoochResponse", "description": null, @@ -51051,35 +50108,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "updateDynamicAnnotationSmooch", - "description": null, - "args": [ - { - "name": "input", - "description": "Parameters for UpdateDynamicAnnotationSmooch", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "UpdateDynamicAnnotationSmoochInput", - "ofType": null - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - } - ], - "type": { - "kind": "OBJECT", - "name": "UpdateDynamicAnnotationSmoochPayload", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "updateDynamicAnnotationSmoochResponse", "description": null, @@ -52498,11 +51526,6 @@ "name": "Dynamic_annotation_slack_message", "ofType": null }, - { - "kind": "OBJECT", - "name": "Dynamic_annotation_smooch", - "ofType": null - }, { "kind": "OBJECT", "name": "Dynamic_annotation_smooch_response", @@ -54606,20 +53629,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "dynamic_annotation_smooch", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "Dynamic", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "dynamic_annotation_smooch_response", "description": null, @@ -55815,67 +54824,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "dynamic_annotations_smooch", - "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": "DynamicConnection", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "dynamic_annotations_smooch_response", "description": null, @@ -68869,21 +67817,21 @@ "description": "TiplineRequest type", "fields": [ { - "name": "annotation", + "name": "associated_graphql_id", "description": null, "args": [ ], "type": { - "kind": "OBJECT", - "name": "Annotation", + "kind": "SCALAR", + "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "annotation_id", + "name": "associated_id", "description": null, "args": [ @@ -68897,7 +67845,7 @@ "deprecationReason": null }, { - "name": "associated_graphql_id", + "name": "associated_type", "description": null, "args": [ @@ -68971,21 +67919,21 @@ "deprecationReason": null }, { - "name": "smooch_report_correction_sent_at", + "name": "smooch_data", "description": null, "args": [ ], "type": { "kind": "SCALAR", - "name": "Int", + "name": "JsonStringType", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "smooch_report_received_at", + "name": "smooch_report_correction_sent_at", "description": null, "args": [ @@ -68999,7 +67947,7 @@ "deprecationReason": null }, { - "name": "smooch_report_sent_at", + "name": "smooch_report_received_at", "description": null, "args": [ @@ -69013,7 +67961,7 @@ "deprecationReason": null }, { - "name": "smooch_report_update_received_at", + "name": "smooch_report_sent_at", "description": null, "args": [ @@ -69027,21 +67975,21 @@ "deprecationReason": null }, { - "name": "smooch_request_type", + "name": "smooch_report_update_received_at", "description": null, "args": [ ], "type": { "kind": "SCALAR", - "name": "String", + "name": "Int", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "smooch_user_external_identifier", + "name": "smooch_request_type", "description": null, "args": [ @@ -69055,7 +68003,7 @@ "deprecationReason": null }, { - "name": "smooch_user_request_language", + "name": "smooch_user_external_identifier", "description": null, "args": [ @@ -69069,7 +68017,7 @@ "deprecationReason": null }, { - "name": "smooch_user_slack_channel_url", + "name": "smooch_user_request_language", "description": null, "args": [ @@ -69083,7 +68031,7 @@ "deprecationReason": null }, { - "name": "updated_at", + "name": "smooch_user_slack_channel_url", "description": null, "args": [ @@ -69097,14 +68045,14 @@ "deprecationReason": null }, { - "name": "value_json", + "name": "updated_at", "description": null, "args": [ ], "type": { "kind": "SCALAR", - "name": "JsonStringType", + "name": "String", "ofType": null }, "isDeprecated": false, @@ -70116,328 +69064,34 @@ "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": "UpdateDynamicAnnotationAnalysisInput", - "description": "Autogenerated input type of UpdateDynamicAnnotationAnalysis", - "fields": null, - "inputFields": [ - { - "name": "id", - "description": null, - "type": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "fragment", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "annotated_id", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "annotated_type", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "action", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "set_attribution", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "action_data", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "set_fields", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "lock_version", - "description": null, - "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "assigned_to_ids", - "description": null, - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "locked", - "description": null, - "type": { - "kind": "SCALAR", - "name": "Boolean", - "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": "UpdateDynamicAnnotationAnalysisPayload", - "description": "Autogenerated return type of UpdateDynamicAnnotationAnalysis", - "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": "dynamic_annotation_analysis", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "Dynamic_annotation_analysis", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "dynamic_annotation_analysisEdge", - "description": null, - "args": [ - - ], - "type": { - "kind": "OBJECT", - "name": "Dynamic_annotation_analysisEdge", - "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": [ @@ -70461,8 +69115,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "UpdateDynamicAnnotationArchiverInput", - "description": "Autogenerated input type of UpdateDynamicAnnotationArchiver", + "name": "UpdateDynamicAnnotationAnalysisInput", + "description": "Autogenerated input type of UpdateDynamicAnnotationAnalysis", "fields": null, "inputFields": [ { @@ -70616,8 +69270,8 @@ }, { "kind": "OBJECT", - "name": "UpdateDynamicAnnotationArchiverPayload", - "description": "Autogenerated return type of UpdateDynamicAnnotationArchiver", + "name": "UpdateDynamicAnnotationAnalysisPayload", + "description": "Autogenerated return type of UpdateDynamicAnnotationAnalysis", "fields": [ { "name": "clientMutationId", @@ -70662,28 +69316,28 @@ "deprecationReason": null }, { - "name": "dynamic_annotation_archiver", + "name": "dynamic_annotation_analysis", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_archiver", + "name": "Dynamic_annotation_analysis", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dynamic_annotation_archiverEdge", + "name": "dynamic_annotation_analysisEdge", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_archiverEdge", + "name": "Dynamic_annotation_analysisEdge", "ofType": null }, "isDeprecated": false, @@ -70755,8 +69409,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "UpdateDynamicAnnotationClipInput", - "description": "Autogenerated input type of UpdateDynamicAnnotationClip", + "name": "UpdateDynamicAnnotationArchiverInput", + "description": "Autogenerated input type of UpdateDynamicAnnotationArchiver", "fields": null, "inputFields": [ { @@ -70910,8 +69564,8 @@ }, { "kind": "OBJECT", - "name": "UpdateDynamicAnnotationClipPayload", - "description": "Autogenerated return type of UpdateDynamicAnnotationClip", + "name": "UpdateDynamicAnnotationArchiverPayload", + "description": "Autogenerated return type of UpdateDynamicAnnotationArchiver", "fields": [ { "name": "clientMutationId", @@ -70956,28 +69610,28 @@ "deprecationReason": null }, { - "name": "dynamic_annotation_clip", + "name": "dynamic_annotation_archiver", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_clip", + "name": "Dynamic_annotation_archiver", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dynamic_annotation_clipEdge", + "name": "dynamic_annotation_archiverEdge", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_clipEdge", + "name": "Dynamic_annotation_archiverEdge", "ofType": null }, "isDeprecated": false, @@ -71049,8 +69703,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "UpdateDynamicAnnotationEmbedCodeInput", - "description": "Autogenerated input type of UpdateDynamicAnnotationEmbedCode", + "name": "UpdateDynamicAnnotationClipInput", + "description": "Autogenerated input type of UpdateDynamicAnnotationClip", "fields": null, "inputFields": [ { @@ -71204,8 +69858,8 @@ }, { "kind": "OBJECT", - "name": "UpdateDynamicAnnotationEmbedCodePayload", - "description": "Autogenerated return type of UpdateDynamicAnnotationEmbedCode", + "name": "UpdateDynamicAnnotationClipPayload", + "description": "Autogenerated return type of UpdateDynamicAnnotationClip", "fields": [ { "name": "clientMutationId", @@ -71250,28 +69904,28 @@ "deprecationReason": null }, { - "name": "dynamic_annotation_embed_code", + "name": "dynamic_annotation_clip", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_embed_code", + "name": "Dynamic_annotation_clip", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dynamic_annotation_embed_codeEdge", + "name": "dynamic_annotation_clipEdge", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_embed_codeEdge", + "name": "Dynamic_annotation_clipEdge", "ofType": null }, "isDeprecated": false, @@ -71343,8 +69997,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "UpdateDynamicAnnotationExtractedTextInput", - "description": "Autogenerated input type of UpdateDynamicAnnotationExtractedText", + "name": "UpdateDynamicAnnotationEmbedCodeInput", + "description": "Autogenerated input type of UpdateDynamicAnnotationEmbedCode", "fields": null, "inputFields": [ { @@ -71498,8 +70152,8 @@ }, { "kind": "OBJECT", - "name": "UpdateDynamicAnnotationExtractedTextPayload", - "description": "Autogenerated return type of UpdateDynamicAnnotationExtractedText", + "name": "UpdateDynamicAnnotationEmbedCodePayload", + "description": "Autogenerated return type of UpdateDynamicAnnotationEmbedCode", "fields": [ { "name": "clientMutationId", @@ -71544,28 +70198,28 @@ "deprecationReason": null }, { - "name": "dynamic_annotation_extracted_text", + "name": "dynamic_annotation_embed_code", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_extracted_text", + "name": "Dynamic_annotation_embed_code", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dynamic_annotation_extracted_textEdge", + "name": "dynamic_annotation_embed_codeEdge", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_extracted_textEdge", + "name": "Dynamic_annotation_embed_codeEdge", "ofType": null }, "isDeprecated": false, @@ -71637,8 +70291,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "UpdateDynamicAnnotationFlagInput", - "description": "Autogenerated input type of UpdateDynamicAnnotationFlag", + "name": "UpdateDynamicAnnotationExtractedTextInput", + "description": "Autogenerated input type of UpdateDynamicAnnotationExtractedText", "fields": null, "inputFields": [ { @@ -71792,8 +70446,8 @@ }, { "kind": "OBJECT", - "name": "UpdateDynamicAnnotationFlagPayload", - "description": "Autogenerated return type of UpdateDynamicAnnotationFlag", + "name": "UpdateDynamicAnnotationExtractedTextPayload", + "description": "Autogenerated return type of UpdateDynamicAnnotationExtractedText", "fields": [ { "name": "clientMutationId", @@ -71838,28 +70492,28 @@ "deprecationReason": null }, { - "name": "dynamic_annotation_flag", + "name": "dynamic_annotation_extracted_text", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_flag", + "name": "Dynamic_annotation_extracted_text", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dynamic_annotation_flagEdge", + "name": "dynamic_annotation_extracted_textEdge", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_flagEdge", + "name": "Dynamic_annotation_extracted_textEdge", "ofType": null }, "isDeprecated": false, @@ -71931,8 +70585,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "UpdateDynamicAnnotationGeolocationInput", - "description": "Autogenerated input type of UpdateDynamicAnnotationGeolocation", + "name": "UpdateDynamicAnnotationFlagInput", + "description": "Autogenerated input type of UpdateDynamicAnnotationFlag", "fields": null, "inputFields": [ { @@ -72086,8 +70740,8 @@ }, { "kind": "OBJECT", - "name": "UpdateDynamicAnnotationGeolocationPayload", - "description": "Autogenerated return type of UpdateDynamicAnnotationGeolocation", + "name": "UpdateDynamicAnnotationFlagPayload", + "description": "Autogenerated return type of UpdateDynamicAnnotationFlag", "fields": [ { "name": "clientMutationId", @@ -72132,28 +70786,28 @@ "deprecationReason": null }, { - "name": "dynamic_annotation_geolocation", + "name": "dynamic_annotation_flag", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_geolocation", + "name": "Dynamic_annotation_flag", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dynamic_annotation_geolocationEdge", + "name": "dynamic_annotation_flagEdge", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_geolocationEdge", + "name": "Dynamic_annotation_flagEdge", "ofType": null }, "isDeprecated": false, @@ -72225,8 +70879,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "UpdateDynamicAnnotationLanguageInput", - "description": "Autogenerated input type of UpdateDynamicAnnotationLanguage", + "name": "UpdateDynamicAnnotationGeolocationInput", + "description": "Autogenerated input type of UpdateDynamicAnnotationGeolocation", "fields": null, "inputFields": [ { @@ -72380,8 +71034,8 @@ }, { "kind": "OBJECT", - "name": "UpdateDynamicAnnotationLanguagePayload", - "description": "Autogenerated return type of UpdateDynamicAnnotationLanguage", + "name": "UpdateDynamicAnnotationGeolocationPayload", + "description": "Autogenerated return type of UpdateDynamicAnnotationGeolocation", "fields": [ { "name": "clientMutationId", @@ -72426,28 +71080,28 @@ "deprecationReason": null }, { - "name": "dynamic_annotation_language", + "name": "dynamic_annotation_geolocation", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_language", + "name": "Dynamic_annotation_geolocation", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dynamic_annotation_languageEdge", + "name": "dynamic_annotation_geolocationEdge", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_languageEdge", + "name": "Dynamic_annotation_geolocationEdge", "ofType": null }, "isDeprecated": false, @@ -72519,8 +71173,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "UpdateDynamicAnnotationMemebusterInput", - "description": "Autogenerated input type of UpdateDynamicAnnotationMemebuster", + "name": "UpdateDynamicAnnotationLanguageInput", + "description": "Autogenerated input type of UpdateDynamicAnnotationLanguage", "fields": null, "inputFields": [ { @@ -72674,8 +71328,8 @@ }, { "kind": "OBJECT", - "name": "UpdateDynamicAnnotationMemebusterPayload", - "description": "Autogenerated return type of UpdateDynamicAnnotationMemebuster", + "name": "UpdateDynamicAnnotationLanguagePayload", + "description": "Autogenerated return type of UpdateDynamicAnnotationLanguage", "fields": [ { "name": "clientMutationId", @@ -72720,28 +71374,28 @@ "deprecationReason": null }, { - "name": "dynamic_annotation_memebuster", + "name": "dynamic_annotation_language", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_memebuster", + "name": "Dynamic_annotation_language", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dynamic_annotation_memebusterEdge", + "name": "dynamic_annotation_languageEdge", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_memebusterEdge", + "name": "Dynamic_annotation_languageEdge", "ofType": null }, "isDeprecated": false, @@ -72813,8 +71467,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "UpdateDynamicAnnotationMetadataInput", - "description": "Autogenerated input type of UpdateDynamicAnnotationMetadata", + "name": "UpdateDynamicAnnotationMemebusterInput", + "description": "Autogenerated input type of UpdateDynamicAnnotationMemebuster", "fields": null, "inputFields": [ { @@ -72968,8 +71622,8 @@ }, { "kind": "OBJECT", - "name": "UpdateDynamicAnnotationMetadataPayload", - "description": "Autogenerated return type of UpdateDynamicAnnotationMetadata", + "name": "UpdateDynamicAnnotationMemebusterPayload", + "description": "Autogenerated return type of UpdateDynamicAnnotationMemebuster", "fields": [ { "name": "clientMutationId", @@ -73014,28 +71668,28 @@ "deprecationReason": null }, { - "name": "dynamic_annotation_metadata", + "name": "dynamic_annotation_memebuster", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_metadata", + "name": "Dynamic_annotation_memebuster", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dynamic_annotation_metadataEdge", + "name": "dynamic_annotation_memebusterEdge", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_metadataEdge", + "name": "Dynamic_annotation_memebusterEdge", "ofType": null }, "isDeprecated": false, @@ -73107,8 +71761,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "UpdateDynamicAnnotationMetricsInput", - "description": "Autogenerated input type of UpdateDynamicAnnotationMetrics", + "name": "UpdateDynamicAnnotationMetadataInput", + "description": "Autogenerated input type of UpdateDynamicAnnotationMetadata", "fields": null, "inputFields": [ { @@ -73262,8 +71916,8 @@ }, { "kind": "OBJECT", - "name": "UpdateDynamicAnnotationMetricsPayload", - "description": "Autogenerated return type of UpdateDynamicAnnotationMetrics", + "name": "UpdateDynamicAnnotationMetadataPayload", + "description": "Autogenerated return type of UpdateDynamicAnnotationMetadata", "fields": [ { "name": "clientMutationId", @@ -73308,28 +71962,28 @@ "deprecationReason": null }, { - "name": "dynamic_annotation_metrics", + "name": "dynamic_annotation_metadata", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_metrics", + "name": "Dynamic_annotation_metadata", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dynamic_annotation_metricsEdge", + "name": "dynamic_annotation_metadataEdge", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_metricsEdge", + "name": "Dynamic_annotation_metadataEdge", "ofType": null }, "isDeprecated": false, @@ -73401,8 +72055,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "UpdateDynamicAnnotationMtInput", - "description": "Autogenerated input type of UpdateDynamicAnnotationMt", + "name": "UpdateDynamicAnnotationMetricsInput", + "description": "Autogenerated input type of UpdateDynamicAnnotationMetrics", "fields": null, "inputFields": [ { @@ -73556,8 +72210,8 @@ }, { "kind": "OBJECT", - "name": "UpdateDynamicAnnotationMtPayload", - "description": "Autogenerated return type of UpdateDynamicAnnotationMt", + "name": "UpdateDynamicAnnotationMetricsPayload", + "description": "Autogenerated return type of UpdateDynamicAnnotationMetrics", "fields": [ { "name": "clientMutationId", @@ -73602,28 +72256,28 @@ "deprecationReason": null }, { - "name": "dynamic_annotation_mt", + "name": "dynamic_annotation_metrics", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_mt", + "name": "Dynamic_annotation_metrics", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dynamic_annotation_mtEdge", + "name": "dynamic_annotation_metricsEdge", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_mtEdge", + "name": "Dynamic_annotation_metricsEdge", "ofType": null }, "isDeprecated": false, @@ -73695,8 +72349,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "UpdateDynamicAnnotationReportDesignInput", - "description": "Autogenerated input type of UpdateDynamicAnnotationReportDesign", + "name": "UpdateDynamicAnnotationMtInput", + "description": "Autogenerated input type of UpdateDynamicAnnotationMt", "fields": null, "inputFields": [ { @@ -73850,8 +72504,8 @@ }, { "kind": "OBJECT", - "name": "UpdateDynamicAnnotationReportDesignPayload", - "description": "Autogenerated return type of UpdateDynamicAnnotationReportDesign", + "name": "UpdateDynamicAnnotationMtPayload", + "description": "Autogenerated return type of UpdateDynamicAnnotationMt", "fields": [ { "name": "clientMutationId", @@ -73896,28 +72550,28 @@ "deprecationReason": null }, { - "name": "dynamic_annotation_report_design", + "name": "dynamic_annotation_mt", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_report_design", + "name": "Dynamic_annotation_mt", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dynamic_annotation_report_designEdge", + "name": "dynamic_annotation_mtEdge", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_report_designEdge", + "name": "Dynamic_annotation_mtEdge", "ofType": null }, "isDeprecated": false, @@ -73989,8 +72643,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "UpdateDynamicAnnotationReverseImageInput", - "description": "Autogenerated input type of UpdateDynamicAnnotationReverseImage", + "name": "UpdateDynamicAnnotationReportDesignInput", + "description": "Autogenerated input type of UpdateDynamicAnnotationReportDesign", "fields": null, "inputFields": [ { @@ -74144,8 +72798,8 @@ }, { "kind": "OBJECT", - "name": "UpdateDynamicAnnotationReverseImagePayload", - "description": "Autogenerated return type of UpdateDynamicAnnotationReverseImage", + "name": "UpdateDynamicAnnotationReportDesignPayload", + "description": "Autogenerated return type of UpdateDynamicAnnotationReportDesign", "fields": [ { "name": "clientMutationId", @@ -74190,28 +72844,28 @@ "deprecationReason": null }, { - "name": "dynamic_annotation_reverse_image", + "name": "dynamic_annotation_report_design", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_reverse_image", + "name": "Dynamic_annotation_report_design", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dynamic_annotation_reverse_imageEdge", + "name": "dynamic_annotation_report_designEdge", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_reverse_imageEdge", + "name": "Dynamic_annotation_report_designEdge", "ofType": null }, "isDeprecated": false, @@ -74283,8 +72937,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "UpdateDynamicAnnotationSlackMessageInput", - "description": "Autogenerated input type of UpdateDynamicAnnotationSlackMessage", + "name": "UpdateDynamicAnnotationReverseImageInput", + "description": "Autogenerated input type of UpdateDynamicAnnotationReverseImage", "fields": null, "inputFields": [ { @@ -74438,8 +73092,8 @@ }, { "kind": "OBJECT", - "name": "UpdateDynamicAnnotationSlackMessagePayload", - "description": "Autogenerated return type of UpdateDynamicAnnotationSlackMessage", + "name": "UpdateDynamicAnnotationReverseImagePayload", + "description": "Autogenerated return type of UpdateDynamicAnnotationReverseImage", "fields": [ { "name": "clientMutationId", @@ -74484,28 +73138,28 @@ "deprecationReason": null }, { - "name": "dynamic_annotation_slack_message", + "name": "dynamic_annotation_reverse_image", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_slack_message", + "name": "Dynamic_annotation_reverse_image", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dynamic_annotation_slack_messageEdge", + "name": "dynamic_annotation_reverse_imageEdge", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_slack_messageEdge", + "name": "Dynamic_annotation_reverse_imageEdge", "ofType": null }, "isDeprecated": false, @@ -74577,8 +73231,8 @@ }, { "kind": "INPUT_OBJECT", - "name": "UpdateDynamicAnnotationSmoochInput", - "description": "Autogenerated input type of UpdateDynamicAnnotationSmooch", + "name": "UpdateDynamicAnnotationSlackMessageInput", + "description": "Autogenerated input type of UpdateDynamicAnnotationSlackMessage", "fields": null, "inputFields": [ { @@ -74732,8 +73386,8 @@ }, { "kind": "OBJECT", - "name": "UpdateDynamicAnnotationSmoochPayload", - "description": "Autogenerated return type of UpdateDynamicAnnotationSmooch", + "name": "UpdateDynamicAnnotationSlackMessagePayload", + "description": "Autogenerated return type of UpdateDynamicAnnotationSlackMessage", "fields": [ { "name": "clientMutationId", @@ -74778,28 +73432,28 @@ "deprecationReason": null }, { - "name": "dynamic_annotation_smooch", + "name": "dynamic_annotation_slack_message", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_smooch", + "name": "Dynamic_annotation_slack_message", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "dynamic_annotation_smoochEdge", + "name": "dynamic_annotation_slack_messageEdge", "description": null, "args": [ ], "type": { "kind": "OBJECT", - "name": "Dynamic_annotation_smoochEdge", + "name": "Dynamic_annotation_slack_messageEdge", "ofType": null }, "isDeprecated": false, diff --git a/test/controllers/elastic_search_10_test.rb b/test/controllers/elastic_search_10_test.rb index fbeb56e98d..4dd96cee74 100644 --- a/test/controllers/elastic_search_10_test.rb +++ b/test/controllers/elastic_search_10_test.rb @@ -254,7 +254,6 @@ def setup test "should filter by keyword and requests fields" do # Reuests fields are username, identifier and content create_annotation_type_and_fields('Smooch User', { 'Id' => ['Text', false], 'Data' => ['JSON', false] }) - create_annotation_type_and_fields('Smooch', { 'Data' => ['JSON', false] }) t = create_team u = create_user create_team_user team: t, user: u, role: 'admin' @@ -302,9 +301,9 @@ def setup create_dynamic_annotation annotated: pm2, annotation_type: 'smooch_user', set_fields: { smooch_user_id: twitter_uid, smooch_user_data: { raw: twitter_data }.to_json }.to_json with_current_user_and_team(u, t) do wa_smooch_data = { 'authorId' => whatsapp_uid, 'text' => 'smooch_request a', 'name' => 'wa_user', 'language' => 'en' } - smooch_pm = create_dynamic_annotation annotated: pm, annotation_type: 'smooch', set_fields: { smooch_data: wa_smooch_data.to_json }.to_json, disable_es_callbacks: false + smooch_pm = create_tipline_request associated: pm, team_id: t.id, language: 'en', smooch_data: wa_smooch_data, disable_es_callbacks: false twitter_smooch_data = { 'authorId' => twitter_uid, 'text' => 'smooch_request b', 'name' => 'melsawy', 'language' => 'fr' } - smooch_pm2 = create_dynamic_annotation annotated: pm2, annotation_type: 'smooch', set_fields: { smooch_data: twitter_smooch_data.to_json }.to_json, disable_es_callbacks: false + smooch_pm2 = create_tipline_request associated: pm2, team_id: t.id, language: 'fr', smooch_data: twitter_smooch_data, disable_es_callbacks: false sleep 2 result = CheckSearch.new({keyword: 'smooch_request', keyword_fields: {fields: ['request_content']}}.to_json) assert_equal [pm.id, pm2.id], result.medias.map(&:id).sort diff --git a/test/controllers/elastic_search_3_test.rb b/test/controllers/elastic_search_3_test.rb index 77a1db3a20..6cabf3cb3f 100644 --- a/test/controllers/elastic_search_3_test.rb +++ b/test/controllers/elastic_search_3_test.rb @@ -217,7 +217,6 @@ def setup test "should sort by clusters requests count" do RequestStore.store[:skip_cached_field_update] = false - create_annotation_type_and_fields('Smooch', { 'Data' => ['JSON', false] }) t = create_team f = create_feed f.teams << t @@ -227,8 +226,8 @@ def setup pm1 = create_project_media team: t pm1_1 = create_project_media team: t pm2 = create_project_media team: t - create_dynamic_annotation annotation_type: 'smooch', annotated: pm1 - create_dynamic_annotation annotation_type: 'smooch', annotated: pm2 + create_tipline_request associated: pm1, team_id: t.id + create_tipline_request associated: pm2, team_id: t.id c1 = create_cluster project_media: pm1 c2 = create_cluster project_media: pm2 c1.project_medias << pm1 @@ -236,8 +235,8 @@ def setup c2.project_medias << pm2 sleep 2 with_current_user_and_team(u, t) do - create_dynamic_annotation annotation_type: 'smooch', annotated: pm1 - create_dynamic_annotation annotation_type: 'smooch', annotated: pm1_1 + create_tipline_request associated: pm1, team_id: t.id + create_tipline_request associated: pm1_1, team_id: t.id sleep 2 es1 = $repository.find(get_es_id(pm1)) es2 = $repository.find(get_es_id(pm2)) diff --git a/test/controllers/elastic_search_8_test.rb b/test/controllers/elastic_search_8_test.rb index 11b75fcf52..6936e38a9f 100644 --- a/test/controllers/elastic_search_8_test.rb +++ b/test/controllers/elastic_search_8_test.rb @@ -36,10 +36,8 @@ def setup create_relationship source_id: pm3.id, target_id: t2_pm3.id, relationship_type: Relationship.suggested_type # Add requests - create_annotation_type_and_fields('Smooch', { 'Data' => ['JSON', false] }) - create_dynamic_annotation annotation_type: 'smooch', annotated: pm2 - 2.times { create_dynamic_annotation(annotation_type: 'smooch', annotated: pm3) } - + create_tipline_request team_id: p.team_id, associated: pm2 + 2.times { create_tipline_request(team_id: p.team_id, associated: pm3) } sleep 2 min_mapping = { diff --git a/test/controllers/graphql_controller_10_test.rb b/test/controllers/graphql_controller_10_test.rb index b053b06e5c..83b468c2d2 100644 --- a/test/controllers/graphql_controller_10_test.rb +++ b/test/controllers/graphql_controller_10_test.rb @@ -784,10 +784,10 @@ def setup t = create_team create_team_user user: u, team: t, role: 'editor' pm = create_project_media team: t - a = create_dynamic_annotation annotation_type: 'smooch', set_fields: { smooch_data: { authorId: '123', language: 'en', received: Time.now.to_f }.to_json }.to_json, annotated: pm + tr = create_tipline_request team_id: t.id, associated: pm, language: 'en', smooch_data: { authorId: '123', language: 'en', received: Time.now.to_f } authenticate_with_user(u) - query = "mutation { sendTiplineMessage(input: { clientMutationId: \"1\", message: \"Hello\", inReplyToId: #{a.id} }) { success } }" + query = "mutation { sendTiplineMessage(input: { clientMutationId: \"1\", message: \"Hello\", inReplyToId: #{tr.id} }) { success } }" post :create, params: { query: query, team: t.slug } assert_response :success @@ -799,10 +799,10 @@ def setup u = create_user t = create_team pm = create_project_media team: t - a = create_dynamic_annotation annotation_type: 'smooch', set_fields: { smooch_data: { authorId: '123', language: 'en', received: Time.now.to_f }.to_json }.to_json, annotated: pm + tr = create_tipline_request team_id: t.id, associated: pm, language: 'en', smooch_data: { authorId: '123', language: 'en', received: Time.now.to_f } authenticate_with_user(u) - query = "mutation { sendTiplineMessage(input: { clientMutationId: \"1\", message: \"Hello\", inReplyToId: #{a.id} }) { success } }" + query = "mutation { sendTiplineMessage(input: { clientMutationId: \"1\", message: \"Hello\", inReplyToId: #{tr.id} }) { success } }" post :create, params: { query: query, team: t.slug } assert_response :success diff --git a/test/controllers/graphql_controller_3_test.rb b/test/controllers/graphql_controller_3_test.rb index e2892e565c..330401c607 100644 --- a/test/controllers/graphql_controller_3_test.rb +++ b/test/controllers/graphql_controller_3_test.rb @@ -235,20 +235,18 @@ def setup test "should retrieve information for grid" do RequestStore.store[:skip_cached_field_update] = false - u = create_user authenticate_with_user(u) t = create_team slug: 'team' create_team_user user: u, team: t p = create_project team: t - m = create_uploaded_image pm = create_project_media project: p, user: create_user, media: m, disable_es_callbacks: false info = { title: random_string, content: random_string }; pm.analysis = info; pm.save! - create_dynamic_annotation(annotation_type: 'smooch', annotated: pm, set_fields: { smooch_data: '{}' }.to_json) + create_tipline_request team_id: t.id, associated: pm, smooch_data: {} pm2 = create_project_media project: p r = create_relationship source_id: pm.id, target_id: pm2.id, relationship_type: Relationship.confirmed_type - create_dynamic_annotation(annotation_type: 'smooch', annotated: pm2, set_fields: { smooch_data: '{}' }.to_json) + create_tipline_request team_id: t.id, associated: pm2, smooch_data: {} create_claim_description project_media: pm, description: 'Test' sleep 10 @@ -348,9 +346,9 @@ def setup pm = create_project_media team: t pm2 = create_project_media team: t authenticate_with_user(u) - create_dynamic_annotation annotation_type: 'smooch', annotated: pm, set_fields: { smooch_data: { 'authorId' => random_string }.to_json }.to_json - create_dynamic_annotation annotation_type: 'smooch', annotated: pm, set_fields: { smooch_data: { 'authorId' => random_string }.to_json }.to_json - create_dynamic_annotation annotation_type: 'smooch', annotated: pm2, set_fields: { smooch_data: { 'authorId' => random_string }.to_json }.to_json + create_tipline_request team_id: t.id, associated: pm, smooch_data: { 'authorId' => random_string } + create_tipline_request team_id: t.id, associated: pm, smooch_data: { 'authorId' => random_string } + create_tipline_request team_id: t.id, associated: pm2, smooch_data: { 'authorId' => random_string } r = create_relationship source_id: pm.id, target_id: pm2.id, relationship_type: Relationship.confirmed_type query = "query { project_media(ids: \"#{pm.id}\") { requests(first: 10) { edges { node { dbid } } } } }" post :create, params: { query: query, team: t.slug } diff --git a/test/models/ability_test.rb b/test/models/ability_test.rb index 28c7d82a05..222baf4e5d 100644 --- a/test/models/ability_test.rb +++ b/test/models/ability_test.rb @@ -103,6 +103,25 @@ def teardown end end + test "#{role} permissions for tipline request" do + u = create_user + t = create_team + t2 = create_team + tu = create_team_user team: t, user: u, role: role + pm = create_project_media team: t + pm2 = create_project_media team: t2 + tr = create_tipline_request team_id: t.id, associated: pm + tr2 = create_tipline_request team_id: t2.id, associated: pm2 + with_current_user_and_team(u, t) do + ability = Ability.new + assert ability.can?(:create, TiplineRequest) + assert ability.can?(:update, tr) + assert ability.can?(:destroy, tr) + assert ability.cannot?(:update, tr2) + assert ability.cannot?(:destroy, tr2) + end + end + test "#{role} permissions for tag" do u = create_user t = create_team diff --git a/test/models/bot/alegre_3_test.rb b/test/models/bot/alegre_3_test.rb index 778fc47fb8..319c3cc77e 100644 --- a/test/models/bot/alegre_3_test.rb +++ b/test/models/bot/alegre_3_test.rb @@ -72,7 +72,6 @@ def teardown } } create_annotation_type_and_fields('Transcription', {}, json_schema) - create_annotation_type_and_fields('Smooch', { 'Data' => ['JSON', true] }) tbi = Bot::Alegre.get_alegre_tbi(@team.id) tbi.set_transcription_similarity_enabled = false tbi.save! @@ -120,8 +119,8 @@ def teardown assert_nil a # Audio item match all required conditions by verify transcription_minimum_requests count RequestStore.store[:skip_cached_field_update] = false - create_dynamic_annotation annotation_type: 'smooch', annotated: pm1 - create_dynamic_annotation annotation_type: 'smooch', annotated: pm1 + create_tipline_request team: @team.id, associated: pm1 + create_tipline_request team: @team.id, associated: pm1 assert Bot::Alegre.run({ data: { dbid: pm1.id }, event: 'create_project_media' }) a = pm1.annotations('transcription').last assert_equal "", a.data['text'] @@ -139,7 +138,6 @@ def teardown } } create_annotation_type_and_fields('Transcription', {}, json_schema) - create_annotation_type_and_fields('Smooch', { 'Data' => ['JSON', true] }) tbi = Bot::Alegre.get_alegre_tbi(@team.id) tbi.set_transcription_similarity_enabled = false tbi.save! @@ -187,8 +185,8 @@ def teardown assert_nil a # Audio item match all required conditions by verify transcription_minimum_requests count RequestStore.store[:skip_cached_field_update] = false - create_dynamic_annotation annotation_type: 'smooch', annotated: pm1 - create_dynamic_annotation annotation_type: 'smooch', annotated: pm1 + create_tipline_request team_id: @pm.team_id, associated: pm1 + create_tipline_request team_id: @pm.team_id, associated: pm1 assert Bot::Alegre.run({ data: { dbid: pm1.id }, event: 'create_project_media' }) a = pm1.annotations('transcription').last expected_last_response = {"job_status"=>"COMPLETED", "transcription"=>"Foo bar"} diff --git a/test/models/bot/alegre_test.rb b/test/models/bot/alegre_test.rb index 3db5f0dd5b..732166a51c 100644 --- a/test/models/bot/alegre_test.rb +++ b/test/models/bot/alegre_test.rb @@ -18,6 +18,7 @@ def setup create_flag_annotation_type create_extracted_text_annotation_type Sidekiq::Testing.inline! + WebMock.disable_net_connect! allow: /#{CheckConfig.get('elasticsearch_host')}|#{CheckConfig.get('storage_endpoint')}/ end def teardown @@ -101,6 +102,7 @@ def teardown test "should unarchive item after running" do stub_configs({ 'alegre_host' => 'http://alegre', 'alegre_token' => 'test' }) do + WebMock.stub_request(:delete, 'http://alegre/text/similarity/').to_return(status: 200, body: '{}') pm = create_project_media pm.archived = CheckArchivedFlags::FlagCodes::PENDING_SIMILARITY_ANALYSIS pm.save! @@ -210,6 +212,8 @@ def teardown end test "should use OCR data for similarity matching" do + WebMock.stub_request(:post, 'http://alegre:3100/text/langid/').with(body: { text: 'Foo bar' }.to_json).to_return(status: 200, body: '{}') + WebMock.stub_request(:post, 'http://alegre:3100/text/similarity/').to_return(status: 200, body: '{}') pm = create_project_media team: @team pm2 = create_project_media team: @team Bot::Alegre.stubs(:get_items_with_similar_description).returns({ pm2.id => {:score=>0.9, :context=>{"team_id"=>@team.id, "field"=>"original_description", "project_media_id"=>pm2.id, "has_custom_id"=>true}, :model=>"elasticsearch"} }) @@ -256,6 +260,8 @@ def teardown # This test to reproduce errbit error CHECK-1218 test "should match to existing parent" do + WebMock.stub_request(:post, 'http://alegre:3100/text/langid/').with(body: { text: 'Foo bar' }.to_json).to_return(status: 200, body: '{}') + WebMock.stub_request(:post, 'http://alegre:3100/text/similarity/').to_return(status: 200, body: '{}') pm_s = create_project_media team: @team pm = create_project_media team: @team pm2 = create_project_media team: @team @@ -271,6 +277,9 @@ def teardown end test "should use transcription data for similarity matching" do + WebMock.stub_request(:post, 'http://alegre:3100/text/langid/').with(body: { text: 'Foo bar' }.to_json).to_return(status: 200, body: '{}') + WebMock.stub_request(:delete, 'http://alegre:3100/text/similarity/').to_return(status: 200, body: '{}') + WebMock.stub_request(:post, 'http://alegre:3100/text/similarity/').to_return(status: 200, body: '{}') json_schema = { type: 'object', required: ['job_name'], @@ -295,6 +304,8 @@ def teardown end test "should check existing relationship before create a new one" do + WebMock.stub_request(:post, 'http://alegre:3100/text/similarity/').to_return(status: 200, body: '{}') + WebMock.stub_request(:post, 'http://alegre:3100/text/langid/').with(body: { text: 'Foo bar' }.to_json).to_return(status: 200, body: '{}') pm = create_project_media team: @team pm2 = create_project_media team: @team pm3 = create_project_media team: @team diff --git a/test/models/bot/smooch_2_test.rb b/test/models/bot/smooch_2_test.rb index f1812c1cf9..ef996a2609 100644 --- a/test/models/bot/smooch_2_test.rb +++ b/test/models/bot/smooch_2_test.rb @@ -28,6 +28,7 @@ def teardown '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: c } ] @@ -61,6 +62,7 @@ def teardown '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: url } ] @@ -99,7 +101,7 @@ def teardown s.save! child = create_project_media project: @project - create_dynamic_annotation annotation_type: 'smooch', annotated: child, set_fields: { smooch_message_id: random_string, smooch_data: { app_id: @app_id, authorId: random_string, language: 'en' }.to_json }.to_json + create_tipline_request team_id: @project.team_id, associated: child, language: 'en', smooch_message_id: random_string, smooch_data: { app_id: @app_id, authorId: random_string, language: 'en' } r = create_relationship source_id: parent.id, target_id: child.id, relationship_type: Relationship.confirmed_type, user: create_user s = child.annotations.where(annotation_type: 'verification_status').last.load assert_equal 'verified', s.status @@ -117,6 +119,7 @@ def teardown '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: random_string } ] @@ -179,6 +182,7 @@ def aasm(_arg) '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: t } ] @@ -229,6 +233,7 @@ def unique_payload(uid, message_text) '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: message_text } ], @@ -263,6 +268,7 @@ def unique_payload(uid, message_text) '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: random_string } ] @@ -372,6 +378,7 @@ def unique_payload(uid, message_text) '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: random_string } ] @@ -412,6 +419,7 @@ def unique_payload(uid, message_text) '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: random_string } ] @@ -439,6 +447,7 @@ def unique_payload(uid, message_text) '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: random_string } ] @@ -507,7 +516,8 @@ def unique_payload(uid, message_text) body: 'Test' }, timestamp: '1623865510', - type: 'text' + type: 'text', + source: { type: "whatsapp" }, } ] } @@ -570,6 +580,7 @@ def unique_payload(uid, message_text) }, timestamp: '1623865510', type: 'image', + source: { type: "whatsapp" }, image: { id: '123456', mime_type: 'image/png' diff --git a/test/models/bot/smooch_3_test.rb b/test/models/bot/smooch_3_test.rb index 33f6de1a98..b11b372b64 100644 --- a/test/models/bot/smooch_3_test.rb +++ b/test/models/bot/smooch_3_test.rb @@ -46,6 +46,7 @@ def teardown '_id': random_string, authorId: random_string, type: 'text', + source: { type: "whatsapp" }, text: random_string } ] @@ -65,8 +66,7 @@ def teardown pm = ProjectMedia.last assert_equal 'undetermined', pm.last_verification_status # Get requests data - sm = pm.get_annotations('smooch').last - requests = DynamicAnnotation::Field.where(annotation_id: sm.id, field_name: 'smooch_data') + requests = TiplineRequest.where(associated_type: 'ProjectMedia', associated_id: pm.id) assert_equal 1, requests.count end @@ -78,12 +78,14 @@ def teardown '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: 'foo', }, { '_id': random_string, authorId: uid, type: 'image', + source: { type: "whatsapp" }, text: 'first image', mediaUrl: @media_url }, @@ -91,6 +93,7 @@ def teardown '_id': random_string, authorId: uid, type: 'image', + source: { type: "whatsapp" }, text: 'second image', mediaUrl: @media_url_2 }, @@ -98,7 +101,8 @@ def teardown '_id': random_string, authorId: uid, type: 'text', - text: 'bar' + text: 'bar', + source: { type: "whatsapp" }, } ] messages.each do |message| @@ -138,6 +142,7 @@ def teardown '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: random_string } ] @@ -182,6 +187,7 @@ def teardown # video message = { type: 'file', + source: { type: "whatsapp" }, text: random_string, mediaUrl: @video_url, mediaType: 'image/jpeg', @@ -189,7 +195,8 @@ def teardown received: 1573082583.219, name: random_string, authorId: random_string, - '_id': random_string + '_id': random_string, + language: 'en', } assert_difference 'ProjectMedia.count' do Bot::Smooch.save_message(message.to_json, @app_id) @@ -201,6 +208,7 @@ def teardown # audio message = { type: 'file', + source: { type: "whatsapp" }, text: random_string, mediaUrl: @audio_url, mediaType: 'image/jpeg', @@ -208,7 +216,8 @@ def teardown received: 1573082583.219, name: random_string, authorId: random_string, - '_id': random_string + '_id': random_string, + language: 'en', } assert_difference 'ProjectMedia.count' do Bot::Smooch.save_message(message.to_json, @app_id) @@ -226,6 +235,7 @@ def teardown '_id': random_string, authorId: random_string, type: 'image', + source: { type: "whatsapp" }, text: random_string, mediaUrl: @media_url_3, mediaSize: UploadedImage.max_size + random_number @@ -235,6 +245,7 @@ def teardown authorId: random_string, type: 'file', mediaType: 'image/jpeg', + source: { type: "whatsapp" }, text: random_string, mediaUrl: @media_url_2, mediaSize: UploadedImage.max_size + random_number @@ -244,6 +255,7 @@ def teardown authorId: random_string, type: 'video', mediaType: 'video/mp4', + source: { type: "whatsapp" }, text: random_string, mediaUrl: @video_url, mediaSize: UploadedVideo.max_size + random_number @@ -253,6 +265,7 @@ def teardown authorId: random_string, type: 'audio', mediaType: 'audio/mpeg', + source: { type: "whatsapp" }, text: random_string, mediaUrl: @audio_url, mediaSize: UploadedAudio.max_size + random_number @@ -288,6 +301,7 @@ def teardown '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: '2' } ], @@ -309,6 +323,7 @@ def teardown '_id': random_string, authorId: random_string, type: 'text', + source: { type: "whatsapp" }, text: random_string, name: nil } @@ -336,7 +351,8 @@ def teardown type: 'file', text: random_string, mediaUrl: @video_url, - mediaType: 'video/mp4' + mediaType: 'video/mp4', + source: { type: "whatsapp" }, }.with_indifferent_access is_supported = Bot::Smooch.supported_message?(message) assert is_supported.slice(:type, :size).all?{ |_k, v| v } @@ -347,7 +363,8 @@ def teardown type: 'file', text: random_string, mediaUrl: @video_url, - mediaType: 'newtype/ogg' + mediaType: 'newtype/ogg', + source: { type: "whatsapp" }, }.with_indifferent_access is_supported = Bot::Smooch.supported_message?(message) assert !is_supported.slice(:type, :size).all?{ |_k, v| v } @@ -357,7 +374,8 @@ def teardown authorId: random_string, type: 'file', text: random_string, - mediaUrl: @video_url + mediaUrl: @video_url, + source: { type: "whatsapp" }, }.with_indifferent_access is_supported = Bot::Smooch.supported_message?(message) assert is_supported.slice(:type, :size).all?{ |_k, v| v } @@ -368,7 +386,8 @@ def teardown type: 'file', text: random_string, mediaUrl: @audio_url, - mediaType: 'audio/mpeg' + mediaType: 'audio/mpeg', + source: { type: "whatsapp" }, }.with_indifferent_access is_supported = Bot::Smooch.supported_message?(message) assert is_supported.slice(:type, :size).all?{ |_k, v| v } @@ -379,7 +398,8 @@ def teardown type: 'file', text: random_string, mediaUrl: @audio_url, - mediaType: 'newtype/mp4' + mediaType: 'newtype/mp4', + source: { type: "whatsapp" }, }.with_indifferent_access is_supported = Bot::Smooch.supported_message?(message) assert !is_supported.slice(:type, :size).all?{ |_k, v| v } @@ -389,7 +409,8 @@ def teardown authorId: random_string, type: 'file', text: random_string, - mediaUrl: @audio_url + mediaUrl: @audio_url, + source: { type: "whatsapp" }, }.with_indifferent_access is_supported = Bot::Smooch.supported_message?(message) assert is_supported.slice(:type, :size).all?{ |_k, v| v } diff --git a/test/models/bot/smooch_4_test.rb b/test/models/bot/smooch_4_test.rb index 92358f3da7..22e634eb1e 100644 --- a/test/models/bot/smooch_4_test.rb +++ b/test/models/bot/smooch_4_test.rb @@ -89,7 +89,6 @@ def teardown end test "should store Smooch conversation id" do - create_annotation_type_and_fields('Smooch', { 'Conversation Id' => ['Text', true] }) conversation_id = random_string result = OpenStruct.new({ conversation: OpenStruct.new({ id: conversation_id })}) SmoochApi::ConversationApi.any_instance.stubs(:get_messages).returns(result) @@ -100,6 +99,7 @@ def teardown '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: random_string } ] @@ -115,12 +115,12 @@ def teardown 'conversationStarted': true } }.to_json - assert_difference "DynamicAnnotation::Field.where(field_name: 'smooch_conversation_id').count" do + assert_difference "TiplineRequest.count" do Bot::Smooch.run(payload) end pm = ProjectMedia.last - a = pm.annotations('smooch').last - assert_equal conversation_id, a.load.get_field_value('smooch_conversation_id') + tr = TiplineRequest.last + assert_equal conversation_id, tr.smooch_conversation_id end SmoochApi::ConversationApi.any_instance.unstub(:get_messages) end @@ -134,6 +134,7 @@ def teardown '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: random_string } ] @@ -175,18 +176,21 @@ def teardown '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: 'foo', }, { '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: 'bar' }, { '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: 'test' } ] @@ -206,10 +210,10 @@ def teardown Bot::Smooch.run(payload) sleep 1 end - assert_difference 'Dynamic.where(annotation_type: "smooch").count' do + assert_difference 'TiplineRequest.count' do Sidekiq::Worker.drain_all end - assert_equal ['bar', 'foo', 'test'], JSON.parse(Dynamic.where(annotation_type: 'smooch').last.get_field_value('smooch_data'))['text'].split(Bot::Smooch::MESSAGE_BOUNDARY).map(&:chomp).sort + assert_equal ['bar', 'foo', 'test'], TiplineRequest.last.smooch_data['text'].split(Bot::Smooch::MESSAGE_BOUNDARY).map(&:chomp).sort end end @@ -254,7 +258,7 @@ def teardown Rails.cache.unstub(:read) Sidekiq::Worker.drain_all assert_equal 'waiting_for_message', sm.state.value - assert_equal ['Hello for the last time', 'ONE ', '2', 'Query'], JSON.parse(Dynamic.where(annotation_type: 'smooch').last.get_field_value('smooch_data'))['text'].split(Bot::Smooch::MESSAGE_BOUNDARY).map(&:chomp) + assert_equal ['Hello for the last time', 'ONE ', '2', 'Query'], TiplineRequest.last.smooch_data['text'].split(Bot::Smooch::MESSAGE_BOUNDARY).map(&:chomp) assert_equal 'Hello for the last time', ProjectMedia.last.text end @@ -376,16 +380,16 @@ def teardown u = create_user is_admin: true t = create_team with_current_user_and_team(u, t) do - d = create_dynamic_annotation annotation_type: 'smooch', set_fields: { smooch_message_id: random_string, smooch_data: { 'authorId' => whatsapp_uid }.to_json }.to_json - assert_equal '+55 12 3456-7890', d.get_field('smooch_data').smooch_user_external_identifier - d = create_dynamic_annotation annotation_type: 'smooch', set_fields: { smooch_message_id: random_string, smooch_data: { 'authorId' => twitter_uid }.to_json }.to_json - assert_equal '@foobar', d.get_field('smooch_data').smooch_user_external_identifier - d = create_dynamic_annotation annotation_type: 'smooch', set_fields: { smooch_message_id: random_string, smooch_data: { 'authorId' => facebook_uid }.to_json }.to_json - assert_equal '123456', d.get_field('smooch_data').smooch_user_external_identifier - d = create_dynamic_annotation annotation_type: 'smooch', set_fields: { smooch_message_id: random_string, smooch_data: { 'authorId' => telegram_uid }.to_json }.to_json - assert_equal '@barfoo', d.get_field('smooch_data').smooch_user_external_identifier - d = create_dynamic_annotation annotation_type: 'smooch', set_fields: { smooch_message_id: random_string, smooch_data: { 'authorId' => other_uid }.to_json }.to_json - assert_equal '', d.get_field('smooch_data').smooch_user_external_identifier + tr = create_tipline_request team_id: t.id, smooch_message_id: random_string, smooch_data: { 'authorId' => whatsapp_uid } + assert_equal '+55 12 3456-7890', tr.smooch_user_external_identifier + tr = create_tipline_request team_id: t.id,smooch_message_id: random_string, smooch_data: { 'authorId' => twitter_uid } + assert_equal '@foobar', tr.smooch_user_external_identifier + tr = create_tipline_request team_id: t.id, smooch_message_id: random_string, smooch_data: { 'authorId' => facebook_uid } + assert_equal '123456', tr.smooch_user_external_identifier + tr = create_tipline_request team_id: t.id, smooch_message_id: random_string, smooch_data: { 'authorId' => telegram_uid } + assert_equal '@barfoo', tr.smooch_user_external_identifier + tr = create_tipline_request team_id: t.id, smooch_message_id: random_string, smooch_data: { 'authorId' => other_uid } + assert_equal '', tr.smooch_user_external_identifier end end @@ -485,9 +489,9 @@ def teardown send_message_to_smooch_bot('4', uid) end Sidekiq::Worker.drain_all - a = Dynamic.where(annotation_type: 'smooch').last - assert_equal 'TiplineResource', a.annotated_type - assert_not_nil a.get_field('smooch_resource_id') + tr = TiplineRequest.last + assert_equal 'TiplineResource', tr.associated_type + assert_not_nil tr.smooch_resource_id end test "should submit short unconfirmed request" do @@ -502,9 +506,8 @@ def teardown assert_no_difference 'ProjectMedia.count' do Sidekiq::Worker.drain_all end - a = Dynamic.where(annotation_type: 'smooch').last - annotated = a.annotated - assert_equal 'Team', a.annotated_type + tr = TiplineRequest.last + assert_equal 'Team', tr.associated_type end test "should submit long unconfirmed request" do @@ -519,13 +522,13 @@ def teardown assert_difference 'ProjectMedia.count' do Sidekiq::Worker.drain_all end - a = Dynamic.where(annotation_type: 'smooch').last - annotated = a.annotated - assert_equal 'ProjectMedia', a.annotated_type - assert_equal CheckArchivedFlags::FlagCodes::UNCONFIRMED, annotated.archived + tr = TiplineRequest.last + associated = tr.associated + assert_equal 'ProjectMedia', tr.associated_type + assert_equal CheckArchivedFlags::FlagCodes::UNCONFIRMED, associated.archived # Verify requests count & demand - assert_equal 1, annotated.requests_count - assert_equal 1, annotated.demand + assert_equal 1, associated.requests_count + assert_equal 1, associated.demand # Auto confirm the media if the same media is sent as a default request Sidekiq::Testing.fake! do send_message_to_smooch_bot(message, uid) @@ -539,8 +542,8 @@ def teardown end Rails.cache.unstub(:read) Sidekiq::Worker.drain_all - assert_equal CheckArchivedFlags::FlagCodes::NONE, annotated.reload.archived - assert_equal 2, annotated.reload.requests_count + assert_equal CheckArchivedFlags::FlagCodes::NONE, associated.reload.archived + assert_equal 2, associated.reload.requests_count # Test resend the same media (should not update archived column) Sidekiq::Testing.fake! do send_message_to_smooch_bot('Hello', uid) @@ -549,8 +552,8 @@ def teardown assert_no_difference 'ProjectMedia.count' do Sidekiq::Worker.drain_all end - assert_equal CheckArchivedFlags::FlagCodes::NONE, annotated.reload.archived - assert_equal 3, annotated.reload.requests_count + assert_equal CheckArchivedFlags::FlagCodes::NONE, associated.reload.archived + assert_equal 3, associated.reload.requests_count end test "should get default TOS message" do @@ -573,11 +576,13 @@ def teardown text: random_string, mediaUrl: @video_url, mediaType: 'video/mp4', + source: { type: "whatsapp" }, role: 'appUser', received: 1573082583.219, name: random_string, authorId: random_string, - '_id': random_string + '_id': random_string, + language: 'en' } medias_count = Media.count assert_difference 'ProjectMedia.count', 1 do @@ -602,9 +607,11 @@ def teardown text = "\vstring for testing\t\n " message = { type: 'text', + source: { type: "whatsapp" }, text: text, authorId: random_string, - '_id': random_string + '_id': random_string, + language: 'en', } assert_difference 'ProjectMedia.count', 1 do Bot::Smooch.save_message(message.to_json, @app_id) @@ -614,9 +621,11 @@ def teardown text = "\vstring FOR teSTIng\t\n " message = { type: 'text', + source: { type: "whatsapp" }, text: text, authorId: random_string, - '_id': random_string + '_id': random_string, + language: 'en', } assert_no_difference 'ProjectMedia.count' do Bot::Smooch.save_message(message.to_json, @app_id) @@ -626,9 +635,11 @@ def teardown text = "\vTEST with existing item \t\n " message = { type: 'text', + source: { type: "whatsapp" }, text: text, authorId: random_string, - '_id': random_string + '_id': random_string, + language: 'en', } assert_no_difference 'ProjectMedia.count' do Bot::Smooch.save_message(message.to_json, @app_id) diff --git a/test/models/bot/smooch_6_test.rb b/test/models/bot/smooch_6_test.rb index 635405a818..df46b2cbe4 100644 --- a/test/models/bot/smooch_6_test.rb +++ b/test/models/bot/smooch_6_test.rb @@ -62,13 +62,13 @@ def assert_state(expected) end def assert_saved_query_type(type) - assert_difference "DynamicAnnotation::Field.where('value LIKE ?', '%#{type}%').count" do + assert_difference "TiplineRequest.where('smooch_request_type LIKE ?', '%#{type}%').count" do Sidekiq::Worker.drain_all end end def assert_no_saved_query - assert_no_difference "Dynamic.where(annotation_type: 'smooch').count" do + assert_no_difference "TiplineRequest.count" do Sidekiq::Worker.drain_all end end @@ -223,7 +223,7 @@ def send_message_outside_24_hours_window(template, pm = nil) Sidekiq::Testing.inline! do send_message 'hello', '1', '1', 'Foo bar', '1' assert_state 'search_result' - assert_difference 'Dynamic.where(annotation_type: "smooch").count + ProjectMedia.count + Relationship.where(relationship_type: Relationship.suggested_type).count', 3 do + assert_difference 'TiplineRequest.count + ProjectMedia.count + Relationship.where(relationship_type: Relationship.suggested_type).count', 3 do send_message '1' end assert_state 'main' @@ -240,7 +240,7 @@ def send_message_outside_24_hours_window(template, pm = nil) Sidekiq::Testing.inline! do send_message 'hello', '1', '1', 'Foo bar foo bar foo bar', '1' assert_state 'search_result' - assert_difference 'Dynamic.where(annotation_type: "smooch").count + ProjectMedia.count + Relationship.where(relationship_type: Relationship.suggested_type).count', 3 do + assert_difference 'TiplineRequest.count + ProjectMedia.count + Relationship.where(relationship_type: Relationship.suggested_type).count', 3 do send_message '1' end assert_state 'main' @@ -257,13 +257,17 @@ def send_message_outside_24_hours_window(template, pm = nil) ProjectMedia.any_instance.stubs(:report_status).returns('published') ProjectMedia.any_instance.stubs(:analysis_published_article_url).returns(random_url) Bot::Alegre.stubs(:get_items_with_similar_media).returns({ @search_result.id => { score: 0.9 } }) - Bot::Smooch.stubs(:bundle_list_of_messages).returns({ 'type' => 'image', 'mediaUrl' => image_url }) + Bot::Smooch.stubs(:bundle_list_of_messages).returns({ 'type' => 'image', 'mediaUrl' => image_url, 'source' => { type: "whatsapp" }, language: 'en' }) CheckS3.stubs(:rewrite_url).returns(random_url) Sidekiq::Testing.inline! do send_message 'hello', '1', '1', 'Image here', '1' assert_state 'search_result' - assert_difference 'Dynamic.where(annotation_type: "smooch").count + ProjectMedia.count + Relationship.where(relationship_type: Relationship.suggested_type).count', 3 do - send_message '1' + assert_difference 'TiplineRequest.count' do + assert_difference 'ProjectMedia.count' do + assert_difference 'Relationship.where(relationship_type: Relationship.suggested_type).count' do + send_message '1' + end + end end assert_state 'main' end @@ -292,7 +296,7 @@ def send_message_outside_24_hours_window(template, pm = nil) Sidekiq::Testing.inline! do send_message 'hello', '1', '1', 'Foo bar', '1' assert_state 'search_result' - assert_difference 'Dynamic.count + ProjectMedia.count', 3 do + assert_difference 'Dynamic.count + TiplineRequest.count + ProjectMedia.count', 3 do send_message '2' end assert_state 'waiting_for_message' @@ -394,6 +398,7 @@ def send_message_outside_24_hours_window(template, pm = nil) type: 'whatsapp', integrationId: random_string }, + language: 'en', } Bot::Smooch.save_message(message.to_json, @app_id, nil, 'menu_options_requests', pm) message = { @@ -410,6 +415,7 @@ def send_message_outside_24_hours_window(template, pm = nil) type: 'messenger', integrationId: random_string }, + language: 'en', } Bot::Smooch.save_message(message.to_json, @app_id, nil, 'menu_options_requests', pm) # verifiy new channel value @@ -429,6 +435,7 @@ def send_message_outside_24_hours_window(template, pm = nil) type: 'messenger', integrationId: random_string }, + language: 'en', } Bot::Smooch.save_message(message.to_json, @app_id, nil, 'menu_options_requests', pm2) # verifiy new channel value @@ -612,8 +619,8 @@ def send_message_outside_24_hours_window(template, pm = nil) send_message url, '1', url, '1' assert_state 'search' Sidekiq::Worker.drain_all - d = Dynamic.where(annotation_type: 'smooch').last - assert_equal 2, JSON.parse(d.get_field_value('smooch_data'))['text'].split("\n#{Bot::Smooch::MESSAGE_BOUNDARY}").select{ |x| x.chomp.strip == url }.size + tr = TiplineRequest.last + assert_equal 2, tr.smooch_data['text'].split("\n#{Bot::Smooch::MESSAGE_BOUNDARY}").select{ |x| x.chomp.strip == url }.size end test "should get search results in different languages" do @@ -871,7 +878,7 @@ def send_message_outside_24_hours_window(template, pm = nil) send_message 'hello', '1' # Sends a first message and confirms language as English send_message 'This is message is so long that it is considered a media' assert_difference 'ProjectMedia.count' do - assert_difference "Dynamic.where(annotation_type: 'smooch').count" do + assert_difference "TiplineRequest.count" do Sidekiq::Worker.drain_all end end @@ -884,7 +891,7 @@ def send_message_outside_24_hours_window(template, pm = nil) send_message 'hello', '1' # Sends a first message and confirms language as English send_message 'Hi, there!' assert_no_difference 'ProjectMedia.count' do - assert_difference "Dynamic.where(annotation_type: 'smooch').count" do + assert_difference "TiplineRequest.count" do Sidekiq::Worker.drain_all end end diff --git a/test/models/bot/smooch_7_test.rb b/test/models/bot/smooch_7_test.rb index e0041231b3..5e3acf755a 100644 --- a/test/models/bot/smooch_7_test.rb +++ b/test/models/bot/smooch_7_test.rb @@ -16,13 +16,13 @@ def teardown test "should update cached field when request is created or deleted" do RequestStore.store[:skip_cached_field_update] = false - create_annotation_type_and_fields('Smooch', { 'Data' => ['JSON', true] }) Sidekiq::Testing.inline! do - pm = create_project_media + t = create_team + pm = create_project_media team: t assert_equal 0, pm.reload.requests_count - d = create_dynamic_annotation annotation_type: 'smooch', annotated: pm + tr = create_tipline_request team_id: t.id, associated: pm assert_equal 1, pm.reload.requests_count - d.destroy + tr.destroy assert_equal 0, pm.reload.requests_count end end @@ -114,7 +114,7 @@ def teardown Time.unstub(:now) end - test "should create smooch annotation for user requests" do + test "should create tipline requests for user requests" do setup_smooch_bot(true) Sidekiq::Testing.fake! do now = Time.now @@ -127,16 +127,14 @@ def teardown assert_equal 'secondary', sm.state.value send_message_to_smooch_bot('1', uid) conditions = { - annotation_type: 'smooch', - annotated_type: @pm_for_menu_option.class.name, - annotated_id: @pm_for_menu_option.id + associated_type: @pm_for_menu_option.class.name, + associated_id: @pm_for_menu_option.id } - assert_difference "Dynamic.where(#{conditions}).count", 1 do + assert_difference "TiplineRequest.where(#{conditions}).count", 1 do Sidekiq::Worker.drain_all end - a = Dynamic.where(conditions).last - f = a.get_field_value('smooch_data') - text = JSON.parse(f)['text'].split("\n#{Bot::Smooch::MESSAGE_BOUNDARY}") + tr = TiplineRequest.where(conditions).last + text = tr.smooch_data['text'].split("\n#{Bot::Smooch::MESSAGE_BOUNDARY}") # Verify that all messages were stored assert_equal 3, text.size assert_equal '1', text.last @@ -147,12 +145,11 @@ def teardown send_message_to_smooch_bot(random_string, uid) send_message_to_smooch_bot(random_string, uid) send_message_to_smooch_bot('1', uid) - assert_difference "Dynamic.where(#{conditions}).count", 1 do + assert_difference "TiplineRequest.where(#{conditions}).count", 1 do Sidekiq::Worker.drain_all end - a = Dynamic.where(conditions).last - f = a.get_field_value('smooch_data') - text = JSON.parse(f)['text'].split("\n#{Bot::Smooch::MESSAGE_BOUNDARY}") + tr = TiplineRequest.where(conditions).last + text = tr.smooch_data['text'].split("\n#{Bot::Smooch::MESSAGE_BOUNDARY}") # verify that all messages stored assert_equal 5, text.size assert_equal '1', text.last @@ -161,13 +158,13 @@ def teardown send_message_to_smooch_bot(random_string, uid) assert_equal 'main', sm.state.value Time.stubs(:now).returns(now + 30.minutes) - assert_difference 'Annotation.where(annotation_type: "smooch").count', 1 do + assert_difference 'TiplineRequest.count', 1 do Sidekiq::Worker.drain_all end send_message_to_smooch_bot(random_string, uid) send_message_to_smooch_bot(random_string, uid) Time.stubs(:now).returns(now + 30.minutes) - assert_difference 'Annotation.where(annotation_type: "smooch").count', 1 do + assert_difference 'TiplineRequest.count', 1 do Sidekiq::Worker.drain_all end end @@ -436,6 +433,7 @@ def teardown '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, text: text } ] @@ -486,7 +484,8 @@ def teardown originalMessageTimestamp: 1573082582, type: 'whatsapp', integrationId: random_string - } + }, + language: 'en', } end Bot::Smooch.save_message(message.call.to_json, @app_id, nil, 'relevant_search_result_requests', pm) @@ -509,6 +508,7 @@ def teardown type: 'whatsapp', integrationId: random_string }, + language: 'en', } end Bot::Smooch.save_message(message.call.to_json, @app_id, nil, 'relevant_search_result_requests', pm2) @@ -526,10 +526,8 @@ def teardown assert_equal 2, es2['tipline_search_results_count'] assert_equal 1, es2['positive_tipline_search_results_count'] # Verify destroy - DynamicAnnotation::Field.where(annotation_type: 'smooch',field_name: 'smooch_request_type') - .where('value IN (?)', ['"irrelevant_search_result_requests"', '"timeout_search_requests"']) - .joins('INNER JOIN annotations a ON a.id = dynamic_annotation_fields.annotation_id') - .where('a.annotated_type = ? AND a.annotated_id = ?', 'ProjectMedia', pm.id).destroy_all + types = ["irrelevant_search_result_requests", "timeout_search_requests"] + TiplineRequest.where(associated_type: 'ProjectMedia', associated_id: pm.id, smooch_request_type: types).destroy_all assert_equal 2, pm.tipline_search_results_count assert_equal 2, pm.positive_tipline_search_results_count end @@ -541,6 +539,7 @@ def teardown '_id': random_string, authorId: random_string, type: 'text', + source: { type: "whatsapp" }, text: random_string } ] @@ -569,11 +568,10 @@ def teardown r.set_fields = { state: 'published' }.to_json r.action = 'republish_and_resend' r.save! - a = pm.annotations('smooch').last.load - smooch_data = a.get_field('smooch_data') - assert_not_nil smooch_data.smooch_report_sent_at - assert_not_nil smooch_data.smooch_report_correction_sent_at - assert_not_nil smooch_data.smooch_request_type + tr = TiplineRequest.last + assert_not_nil tr.smooch_report_sent_at + assert_not_nil tr.smooch_report_correction_sent_at + assert_not_nil tr.smooch_request_type end test "should include claim_description_content in smooch search" do @@ -591,4 +589,19 @@ def teardown results = Bot::Smooch.search_by_keywords_for_similar_published_fact_checks(query.split(), nil, [t.id]) assert_equal [pm.id], results.map(&:id) end + + test "should rescue when raise error on tipline request creation" do + TiplineRequest.any_instance.stubs(:save!).raises(ActiveRecord::RecordNotUnique) + t = create_team + pm = create_project_media team: t + u = create_user + create_team_user team: t, user: u, role: 'admin' + with_current_user_and_team(u, t) do + fields = { 'smooch_request_type' => 'default_requests', 'smooch_message_id' => random_string, 'smooch_data' => { authorId: random_string, language: 'en', source: { type: "whatsapp" }, } } + assert_no_difference 'TiplineRequest.count' do + Bot::Smooch.create_tipline_requests(pm, nil, fields) + end + end + TiplineRequest.any_instance.unstub(:save!) + end end diff --git a/test/models/bot/smooch_8_test.rb b/test/models/bot/smooch_8_test.rb deleted file mode 100644 index 489ea3e689..0000000000 --- a/test/models/bot/smooch_8_test.rb +++ /dev/null @@ -1,27 +0,0 @@ -require_relative '../../test_helper' -require 'sidekiq/testing' - -class Bot::Smooch8Test < ActiveSupport::TestCase - def setup - WebMock.disable_net_connect! allow: /#{CheckConfig.get('elasticsearch_host')}|#{CheckConfig.get('storage_endpoint')}/ - end - - def teardown - end - - test "should not store duplicated Smooch requests" do - create_annotation_type_and_fields('Smooch', { - 'Data' => ['JSON', false], - 'Message Id' => ['Text', false] - }) - - pm = create_project_media - fields = { 'smooch_message_id' => random_string, 'smooch_data' => '{}' } - assert_difference 'Annotation.count' do - Bot::Smooch.create_smooch_annotations(pm, nil, fields) - end - assert_no_difference 'Annotation.count' do - Bot::Smooch.create_smooch_annotations(pm, nil, fields) - end - end -end diff --git a/test/models/bot/smooch_rules_test.rb b/test/models/bot/smooch_rules_test.rb index 379fedd6b6..d10c6358b1 100644 --- a/test/models/bot/smooch_rules_test.rb +++ b/test/models/bot/smooch_rules_test.rb @@ -131,6 +131,8 @@ def teardown '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, + language: 'en', text: ([random_string] * 10).join(' ') } ] @@ -157,6 +159,8 @@ def teardown '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, + language: 'en', text: ([random_string] * 3).join(' ') + ' pLease?' } ] @@ -182,6 +186,8 @@ def teardown '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, + language: 'en', text: random_string } ] @@ -211,6 +217,8 @@ def teardown '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, + language: 'en', text: quote } ] @@ -235,6 +243,8 @@ def teardown '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, + language: 'en', text: random_number.to_s + ' ' + random_string } ] @@ -259,6 +269,8 @@ def teardown '_id': random_string, authorId: uid, type: 'text', + source: { type: "whatsapp" }, + language: 'en', text: [random_string, random_string, random_string, 'bad word', random_string, random_string].join(' ') } ] diff --git a/test/models/bot/smooch_test.rb b/test/models/bot/smooch_test.rb index c587c90d6d..0bc83003ec 100644 --- a/test/models/bot/smooch_test.rb +++ b/test/models/bot/smooch_test.rb @@ -61,12 +61,14 @@ def teardown authorId: id2, type: 'audio', text: random_string, + source: { type: "whatsapp" }, mediaUrl: @audio_url }, { '_id': random_string, authorId: id, type: 'text', + source: { type: "whatsapp" }, text: 'This is a test claim' }, { @@ -74,18 +76,21 @@ def teardown authorId: id, type: 'image', text: random_string, + source: { type: "whatsapp" }, mediaUrl: @media_url }, { '_id': random_string, authorId: id, type: 'text', + source: { type: "whatsapp" }, text: "#{random_string} #{@link_url} #{random_string}" }, { '_id': random_string, authorId: id, type: 'text', + source: { type: "whatsapp" }, text: 'This is a test claim' }, { @@ -93,18 +98,21 @@ def teardown authorId: id, type: 'image', text: random_string, + source: { type: "whatsapp" }, mediaUrl: @media_url }, { '_id': random_string, authorId: id, type: 'text', + source: { type: "whatsapp" }, text: "#{random_string} #{@link_url} #{random_string}" }, { '_id': random_string, authorId: id2, type: 'text', + source: { type: "whatsapp" }, text: 'This is a test claim' }, { @@ -112,6 +120,7 @@ def teardown authorId: id2, type: 'image', text: random_string, + source: { type: "whatsapp" }, mediaUrl: @media_url }, { @@ -120,6 +129,7 @@ def teardown type: 'file', text: random_string, mediaUrl: @media_url, + source: { type: "whatsapp" }, mediaType: 'image/jpeg' }, { @@ -128,18 +138,21 @@ def teardown type: 'file', text: random_string, mediaUrl: @media_url, + source: { type: "whatsapp" }, mediaType: 'application/pdf' }, { '_id': random_string, authorId: id2, type: 'text', + source: { type: "whatsapp" }, text: "#{random_string} #{@link_url} #{random_string}" }, { '_id': random_string, authorId: id2, type: 'text', + source: { type: "whatsapp" }, text: 'This is a test claim' }, { @@ -147,6 +160,7 @@ def teardown authorId: id2, type: 'image', text: random_string, + source: { type: "whatsapp" }, mediaUrl: @media_url }, { @@ -154,36 +168,42 @@ def teardown authorId: id2, type: 'video', text: random_string, + source: { type: "whatsapp" }, mediaUrl: @video_url }, { '_id': random_string, authorId: id2, type: 'text', + source: { type: "whatsapp" }, text: "#{random_string} #{@link_url} #{random_string}" }, { '_id': random_string, authorId: id2, type: 'text', + source: { type: "whatsapp" }, text: "#{random_string} #{@link_url_2} #montag #{random_string}" }, { '_id': random_string, authorId: id3, type: 'text', + source: { type: "whatsapp" }, text: "#{random_string} #{@link_url_2.gsub(/^https?:\/\//, '')} #teamtag #{random_string}" }, { '_id': random_string, authorId: id2, type: 'text', + source: { type: "whatsapp" }, text: 'This #teamtag is another #hashtag claim' }, { '_id': random_string, authorId: id3, type: 'text', + source: { type: "whatsapp" }, text: 'This #teamtag is another #hashtag CLAIM' }, { @@ -192,6 +212,7 @@ def teardown type: 'file', text: random_string, mediaUrl: @video_url, + source: { type: "whatsapp" }, mediaType: 'video/mp4' }, { @@ -200,6 +221,7 @@ def teardown type: 'file', text: random_string, mediaUrl: @audio_url, + source: { type: "whatsapp" }, mediaType: 'audio/mpeg' } ] @@ -208,7 +230,7 @@ def teardown tt_montag = create_tag_text text: 'montag', team_id: @team.id assert_difference 'ProjectMedia.count', 7 do - assert_difference 'Annotation.where(annotation_type: "smooch").count', 22 do + assert_difference 'TiplineRequest.count', 22 do assert_no_difference 'Comment.length' do messages.each do |message| uid = message[:authorId] @@ -282,7 +304,8 @@ def teardown '_id': random_string, authorId: uid, type: 'text', - text: random_string + text: random_string, + source: { type: "whatsapp" }, } ] payload = { @@ -490,7 +513,8 @@ def teardown '_id': random_string, authorId: uid, type: 'text', - text: random_string + text: random_string, + source: { type: "whatsapp" }, } ] payload = { @@ -535,7 +559,8 @@ def teardown '_id': random_string, authorId: uid, type: 'text', - text: random_string + text: random_string, + source: { type: "whatsapp" }, } ] payload = { @@ -554,11 +579,9 @@ def teardown assert Bot::Smooch.run(payload) pm = ProjectMedia.last - sm = pm.get_annotations('smooch').last - df = DynamicAnnotation::Field.where(annotation_id: sm.id, field_name: 'smooch_data').last - assert_not_nil df - assert_equal 0, df.reload.smooch_report_received_at - assert_nil df.reload.smooch_report_update_received_at + tr = pm.tipline_requests.last + assert_equal 0, tr.reload.smooch_report_received_at + assert_equal 0, tr.reload.smooch_report_update_received_at r = publish_report(pm) assert_equal 0, r.reload.sent_count msg_id = random_string @@ -566,7 +589,7 @@ def teardown fallback_template: 'fact_check_report', project_media_id: pm.id }.to_json) - assert_nil DynamicAnnotation::Field.where(field_name: 'smooch_report_received').last + assert_equal 0, tr.reload.smooch_report_received_at payload = { trigger: 'message:delivery:channel', @@ -585,26 +608,18 @@ def teardown } assert Bot::Smooch.run(payload.to_json) - f1 = DynamicAnnotation::Field.where(field_name: 'smooch_report_received').last - assert_not_nil f1 - t1 = f1.value - assert_equal t1, df.reload.smooch_report_received_at - assert_nil df.reload.smooch_report_update_received_at + assert tr.reload.smooch_report_received_at > 0 + assert_equal 0, tr.reload.smooch_report_update_received_at assert_equal 1, r.reload.sent_count - sleep 1 # Process TiplineMessage creation in background to avoid duplication exception Sidekiq::Testing.fake! do assert Bot::Smooch.run(payload.to_json) - f2 = DynamicAnnotation::Field.where(field_name: 'smooch_report_received').last - assert_equal f1, f2 - t2 = f2.value - assert_equal t2, df.reload.smooch_report_received_at - assert_equal t2, df.reload.smooch_report_update_received_at + tr2 = pm.tipline_requests.last + assert_equal tr, tr2 + assert tr2.smooch_report_update_received_at > 0 assert_equal 1, r.reload.sent_count - - assert t2 > t1 end end @@ -720,7 +735,8 @@ def teardown '_id': random_string, authorId: uid, type: 'text', - text: random_string + text: random_string, + source: { type: "whatsapp" }, } ] payload = { @@ -751,7 +767,8 @@ def teardown '_id': random_string, authorId: uid, type: 'text', - text: random_string + text: random_string, + source: { type: "whatsapp" }, } ] payload = { @@ -768,8 +785,7 @@ def teardown }.to_json assert Bot::Smooch.run(payload) pm = ProjectMedia.last - sm = pm.get_annotations('smooch').last.load - f = sm.get_field('smooch_data') - assert_equal 'en', f.smooch_user_request_language + tr = pm.tipline_requests.last + assert_equal 'en', tr.smooch_user_request_language end end diff --git a/test/models/cluster_test.rb b/test/models/cluster_test.rb index ea6db9e2be..3f3f78f892 100644 --- a/test/models/cluster_test.rb +++ b/test/models/cluster_test.rb @@ -99,20 +99,19 @@ def setup end test "should get requests count" do - create_annotation_type_and_fields('Smooch', { 'Data' => ['JSON', true] }) RequestStore.store[:skip_cached_field_update] = false t = create_team Sidekiq::Testing.inline! do c = create_cluster pm = create_project_media team: t - 2.times { create_dynamic_annotation(annotation_type: 'smooch', annotated: pm) } + 2.times { create_tipline_request(team_id: t.id, associated: pm) } pm2 = create_project_media team: t - 2.times { create_dynamic_annotation(annotation_type: 'smooch', annotated: pm2) } + 2.times { create_tipline_request(team_id: t.id, associated: pm2) } c.project_medias << pm c.project_medias << pm2 assert_equal 4, c.requests_count assert_equal 4, c.requests_count(true) - d = create_dynamic_annotation annotation_type: 'smooch', annotated: pm + d = create_tipline_request team_id: t.id, associated: pm assert_equal 5, c.requests_count assert_equal 5, c.requests_count(true) d.destroy! diff --git a/test/models/dynamic_annotation/field_test.rb b/test/models/dynamic_annotation/field_test.rb index ce938c6d3e..c87915efa6 100644 --- a/test/models/dynamic_annotation/field_test.rb +++ b/test/models/dynamic_annotation/field_test.rb @@ -232,7 +232,6 @@ def field_formatter_name_response end test "should get smooch user slack channel url" do - create_annotation_type_and_fields('Smooch', { 'Data' => ['JSON', false] }) create_annotation_type_and_fields('Smooch User', { 'Data' => ['JSON', false], 'Slack Channel Url' => ['Text', true], @@ -247,11 +246,10 @@ def field_formatter_name_response set_fields = { smooch_user_id: author_id, smooch_user_data: { id: author_id }.to_json, smooch_user_slack_channel_url: url }.to_json d = create_dynamic_annotation annotated: t, annotation_type: 'smooch_user', set_fields: set_fields with_current_user_and_team(u, t) do - ds = create_dynamic_annotation annotation_type: 'smooch', annotated: pm, set_fields: { smooch_data: { 'authorId' => author_id }.to_json }.to_json - f = ds.get_field('smooch_data') - assert_equal url, f.smooch_user_slack_channel_url + tr = create_tipline_request team_id: t.id, associated: pm, tipline_user_uid: author_id, smooch_data: { 'authorId' => author_id } + assert_equal url, tr.smooch_user_slack_channel_url assert 1, Rails.cache.delete_matched("SmoochUserSlackChannelUrl:Team:*") - assert_equal url, f.smooch_user_slack_channel_url + assert_equal url, tr.smooch_user_slack_channel_url end end diff --git a/test/models/project_media_2_test.rb b/test/models/project_media_2_test.rb index 66bf6d5c39..f8862acaf6 100644 --- a/test/models/project_media_2_test.rb +++ b/test/models/project_media_2_test.rb @@ -146,7 +146,6 @@ def setup RequestStore.store[:skip_cached_field_update] = false # sortable fields are [linked_items_count, last_seen and share_count] setup_elasticsearch - create_annotation_type_and_fields('Smooch', { 'Data' => ['JSON', false] }) Rails.stubs(:env).returns('development'.inquiry) team = create_team p = create_project team: team @@ -155,7 +154,7 @@ def setup assert_equal 1, result['linked_items_count'] assert_equal pm.created_at.to_i, result['last_seen'] assert_equal pm.reload.last_seen, pm.read_attribute(:last_seen) - t = t0 = create_dynamic_annotation(annotation_type: 'smooch', annotated: pm).created_at.to_i + t = t0 = create_tipline_request(team_id: team.id, associated: pm).created_at.to_i result = $repository.find(get_es_id(pm)) assert_equal t, result['last_seen'] assert_equal pm.reload.last_seen, pm.read_attribute(:last_seen) @@ -170,7 +169,7 @@ def setup assert_equal t, result['last_seen'] assert_equal pm.reload.last_seen, pm.read_attribute(:last_seen) - t = create_dynamic_annotation(annotation_type: 'smooch', annotated: pm2).created_at.to_i + t = create_tipline_request(team_id: team.id, associated: pm2).created_at.to_i result = $repository.find(get_es_id(pm)) assert_equal t, result['last_seen'] assert_equal pm.reload.last_seen, pm.read_attribute(:last_seen) @@ -203,8 +202,8 @@ def setup t0 = pm.created_at.to_i # pm.last_seen should equal pm.created_at if no tipline request (aka 'smooch' annotation) assert_queries(0, '=') { assert_equal(t0, pm.last_seen) } - t1 = create_dynamic_annotation(annotation_type: 'smooch', annotated: pm).created_at.to_i - # pm.last_seen should equal pm smooch annotation created_at if item is not related + t1 = create_tipline_request(team_id: team.id, associated: pm).created_at.to_i + # pm.last_seen should equal pm tipline request created_at if item is not related assert_queries(0, '=') { assert_equal(t1, pm.last_seen) } pm2 = create_project_media team: team t2 = pm2.created_at.to_i @@ -214,11 +213,11 @@ def setup # pm is now a parent and pm2 its child with no smooch annotation, so pm.last_seen should match pm2.created_at assert_queries(0, '=') { assert_equal(t2, pm.last_seen) } # adding a smooch annotation to pm2 should update parent last_seen - t3 = create_dynamic_annotation(annotation_type: 'smooch', annotated: pm2).created_at.to_i + t3 = create_tipline_request(team_id: team.id, associated: pm2).created_at.to_i assert_queries(0, '=') { assert_equal(t3, pm.last_seen) } # now let's add a second child pm3... pm3 = create_project_media team: team - t4 = create_dynamic_annotation(annotation_type: 'smooch', annotated: pm3).created_at.to_i + t4 = create_tipline_request(team_id: team.id, associated: pm3).created_at.to_i r2 = create_relationship source_id: pm.id, target_id: pm3.id, relationship_type: Relationship.confirmed_type # pm3.last_seen should equal pm3 smooch annotation created_at assert_queries(0, '=') { assert_equal(t4, pm3.last_seen) } @@ -237,4 +236,4 @@ def setup end end -end \ No newline at end of file +end diff --git a/test/models/project_media_6_test.rb b/test/models/project_media_6_test.rb index f560746e0c..dc666594d0 100644 --- a/test/models/project_media_6_test.rb +++ b/test/models/project_media_6_test.rb @@ -343,16 +343,15 @@ def setup RequestStore.store[:skip_cached_field_update] = false team = create_team p = create_project team: team - create_annotation_type_and_fields('Smooch', { 'Data' => ['JSON', false] }) pm = create_project_media team: team, project_id: p.id, disable_es_callbacks: false ms_pm = get_es_id(pm) assert_queries(0, '=') { assert_equal(0, pm.demand) } - create_dynamic_annotation annotation_type: 'smooch', annotated: pm + create_tipline_request team: team.id, associated: pm assert_queries(0, '=') { assert_equal(1, pm.demand) } pm2 = create_project_media team: team, project_id: p.id, disable_es_callbacks: false ms_pm2 = get_es_id(pm2) assert_queries(0, '=') { assert_equal(0, pm2.demand) } - 2.times { create_dynamic_annotation(annotation_type: 'smooch', annotated: pm2) } + 2.times { create_tipline_request(team_id: team.id, associated: pm2) } assert_queries(0, '=') { assert_equal(2, pm2.demand) } # test sorting result = $repository.find(ms_pm) @@ -371,13 +370,13 @@ def setup pm3 = create_project_media team: team, project_id: p.id ms_pm3 = get_es_id(pm3) assert_queries(0, '=') { assert_equal(0, pm3.demand) } - 2.times { create_dynamic_annotation(annotation_type: 'smooch', annotated: pm3) } + 2.times { create_tipline_request(team_id: team.id, associated: pm3) } assert_queries(0, '=') { assert_equal(2, pm3.demand) } create_relationship source_id: pm.id, target_id: pm3.id, relationship_type: Relationship.confirmed_type assert_queries(0, '=') { assert_equal(5, pm.demand) } assert_queries(0, '=') { assert_equal(5, pm2.demand) } assert_queries(0, '=') { assert_equal(5, pm3.demand) } - create_dynamic_annotation annotation_type: 'smooch', annotated: pm3 + create_tipline_request team_id: team.id, associated: pm3 assert_queries(0, '=') { assert_equal(6, pm.demand) } assert_queries(0, '=') { assert_equal(6, pm2.demand) } assert_queries(0, '=') { assert_equal(6, pm3.demand) } diff --git a/test/models/tipline_request_test.rb b/test/models/tipline_request_test.rb new file mode 100644 index 0000000000..3bbfdd22c8 --- /dev/null +++ b/test/models/tipline_request_test.rb @@ -0,0 +1,56 @@ +require_relative '../test_helper' + +class TiplineRequestTest < ActiveSupport::TestCase + test "should create tipline request" do + assert_difference 'TiplineRequest.count' do + create_tipline_request + end + # validate smooch_request_type, language and platform + assert_raises ActiveRecord::RecordInvalid do + create_tipline_request smooch_request_type: nil, smooch_data: { language: 'en', authorId: random_string, source: { type: 'whatsapp' } } + end + assert_raises ActiveRecord::RecordInvalid do + create_tipline_request language: nil, smooch_data: { authorId: random_string, source: { type: 'whatsapp' } } + end + assert_raises ActiveRecord::RecordInvalid do + create_tipline_request platform: nil, smooch_data: { language: 'en', authorId: random_string } + end + # validate smooch_request_type and platform values + assert_no_difference 'TiplineRequest.count' do + assert_raises ActiveRecord::RecordInvalid do + create_tipline_request smooch_request_type: 'invalid_type' + end + end + assert_no_difference 'TiplineRequest.count' do + assert_raises ActiveRecord::RecordInvalid do + create_tipline_request platform: random_string, smooch_data: { language: 'en', authorId: random_string } + end + end + end + + test "should set user and team" do + t = create_team + u = create_user + create_team_user team: t, user: u, role: 'admin' + with_current_user_and_team(u, t) do + tr = create_tipline_request team_id: nil + assert_equal t.id, tr.team_id + assert_equal u.id, tr.user_id + end + end + + test "should set smooch data fields" do + author_id = random_string + platform = 'whatsapp' + smooch_data = { language: 'en', authorId: author_id, source: { type: platform } } + tr = create_tipline_request smooch_data: smooch_data + assert_equal 'en', tr.language + assert_equal author_id, tr.tipline_user_uid + assert_equal platform, tr.platform + end + + test "should get associated GraphQL ID" do + tr = create_tipline_request + assert_kind_of String, tr.associated_graphql_id + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 695d08ad75..26e040e76e 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -159,7 +159,7 @@ def before_all # This will run before any test def setup - [Account, Media, ProjectMedia, User, Source, Annotation, Team, TeamUser, Relationship, Project, TiplineResource].each{ |klass| klass.delete_all } + [Account, Media, ProjectMedia, User, Source, Annotation, Team, TeamUser, Relationship, Project, TiplineResource, TiplineRequest].each{ |klass| klass.delete_all } # Some of our non-GraphQL tests rely on behavior that this requires. As a result, # we'll keep it around for now and just recreate any needed dynamic annotation data @@ -846,14 +846,6 @@ def setup_smooch_bot(menu = false, extra_settings = {}) DynamicAnnotation::FieldType.delete_all DynamicAnnotation::Field.delete_all create_verification_status_stuff - create_annotation_type_and_fields('Smooch', { - 'Data' => ['JSON', false], - 'Report Received' => ['Timestamp', true], - 'Request Type' => ['Text', true], - 'Resource Id' => ['Text', true], - 'Report correction sent at' => ['Timestamp', true], - 'Report sent at' => ['Timestamp', true] - }) create_annotation_type_and_fields('Smooch Response', { 'Data' => ['JSON', true] }) create_annotation_type annotation_type: 'reverse_image', label: 'Reverse Image' WebMock.disable_net_connect! allow: /#{CheckConfig.get('elasticsearch_host')}|#{CheckConfig.get('storage_endpoint')}/ @@ -1051,7 +1043,9 @@ def send_message_to_smooch_bot(message = random_string, user = random_string, ex '_id': random_string, authorId: user, type: 'text', - text: message + text: message, + source: { type: "whatsapp" }, + language: 'en', }.merge(extra) ] payload = {