From e1bb40222f98260b736035724c7006b2041ee5f8 Mon Sep 17 00:00:00 2001 From: Caio <117518+caiosba@users.noreply.github.com> Date: Wed, 25 Oct 2023 20:28:04 -0300 Subject: [PATCH 1/4] Adding GraphQL mutations to add and remove NLU keywords to/from tipline menu options. Only super-admins can use these mutations. Reference: CV2-3709. --- app/graph/mutations/nlu_mutations.rb | 43 +++ app/graph/types/mutation_type.rb | 3 + app/lib/smooch_nlu.rb | 6 +- app/lib/smooch_nlu_menus.rb | 7 +- lib/relay.idl | 62 ++++ public/relay.json | 314 ++++++++++++++++++ .../controllers/graphql_controller_10_test.rb | 66 ++++ 7 files changed, 495 insertions(+), 6 deletions(-) create mode 100644 app/graph/mutations/nlu_mutations.rb diff --git a/app/graph/mutations/nlu_mutations.rb b/app/graph/mutations/nlu_mutations.rb new file mode 100644 index 0000000000..6bd6a436ba --- /dev/null +++ b/app/graph/mutations/nlu_mutations.rb @@ -0,0 +1,43 @@ +module NluMutations + class ToggleKeywordInTiplineMenu < Mutations::BaseMutation + argument :language, GraphQL::Types::String, required: true + argument :keyword, GraphQL::Types::String, required: true + argument :menu, GraphQL::Types::String, required: true # "main" or "secondary" + argument :menu_option_index, GraphQL::Types::Int, required: true # zero-based... the order is the same displayed in the tipline and in the tipline settings page + + field :success, GraphQL::Types::Boolean, null: true + + def resolve(language:, menu:, menu_option_index:, keyword:) + begin + if User.current.is_admin + nlu = SmoochNlu.new(Team.current.slug) + nlu.enable! + nlu.add_keyword_to_menu_option(language, menu, menu_option_index, keyword) if toggle == :add + nlu.remove_keyword_from_menu_option(language, menu, menu_option_index, keyword) if toggle == :remove + puts 'HERE 1' + { success: true } + else + puts 'HERE 2' + { success: false } + end + rescue + puts 'HERE 3' + { success: false } + end + end + end + + class AddKeywordToTiplineMenu < ToggleKeywordInTiplineMenu + def toggle + puts 'HERE 4' + :add + end + end + + class RemoveKeywordFromTiplineMenu < ToggleKeywordInTiplineMenu + def toggle + puts 'HERE 5' + :remove + end + end +end diff --git a/app/graph/types/mutation_type.rb b/app/graph/types/mutation_type.rb index ffc94151fb..353cdbf0f9 100644 --- a/app/graph/types/mutation_type.rb +++ b/app/graph/types/mutation_type.rb @@ -158,4 +158,7 @@ class MutationType < BaseObject field :destroyTiplineResource, mutation: TiplineResourceMutations::Destroy field :sendTiplineMessage, mutation: TiplineMessageMutations::Send + + field :addNluKeywordToTiplineMenu, mutation: NluMutations::AddKeywordToTiplineMenu + field :removeNluKeywordFromTiplineMenu, mutation: NluMutations::RemoveKeywordFromTiplineMenu end diff --git a/app/lib/smooch_nlu.rb b/app/lib/smooch_nlu.rb index cf7b3e1b76..d0003a020d 100644 --- a/app/lib/smooch_nlu.rb +++ b/app/lib/smooch_nlu.rb @@ -5,9 +5,9 @@ class SmoochBotNotInstalledError < ::ArgumentError # FIXME: Make it more flexible # FIXME: Once we support paraphrase-multilingual-mpnet-base-v2 make it the only model used ALEGRE_MODELS_AND_THRESHOLDS = { - # Bot::Alegre::ELASTICSEARCH_MODEL => 0.8, Sometimes this is easier for local development - Bot::Alegre::OPENAI_ADA_MODEL => 0.8, - Bot::Alegre::MEAN_TOKENS_MODEL => 0.6 + Bot::Alegre::ELASTICSEARCH_MODEL => 0.8 # , Sometimes this is easier for local development + # Bot::Alegre::OPENAI_ADA_MODEL => 0.8, + # Bot::Alegre::MEAN_TOKENS_MODEL => 0.6 } include SmoochNluMenus diff --git a/app/lib/smooch_nlu_menus.rb b/app/lib/smooch_nlu_menus.rb index 3857f1b6e0..236483e6a4 100644 --- a/app/lib/smooch_nlu_menus.rb +++ b/app/lib/smooch_nlu_menus.rb @@ -13,7 +13,7 @@ def remove_keyword_from_menu_option(language, menu, menu_option_index, keyword) update_menu_option_keywords(language, menu, menu_option_index, keyword, 'remove') end - def list_menu_keywords(languages = nil, menus = nil) + def list_menu_keywords(languages = nil, menus = nil, include_empty = true) if languages.nil? languages = @smooch_bot_installation.get_smooch_workflows.map { |w| w['smooch_workflow_language'] } elsif languages.is_a? String @@ -33,12 +33,13 @@ def list_menu_keywords(languages = nil, menus = nil) output[language][menu] = [] i = 0 workflow.fetch("smooch_state_#{menu}",{}).fetch('smooch_menu_options', []).each do |option| + keywords = option.dig('smooch_menu_option_nlu_keywords').to_a output[language][menu] << { 'index' => i, 'title' => option.dig('smooch_menu_option_label'), - 'keywords' => option.dig('smooch_menu_option_nlu_keywords').to_a, + 'keywords' => keywords, 'id' => option.dig('smooch_menu_option_id'), - } + } if include_empty || !keywords.blank? i += 1 end end diff --git a/lib/relay.idl b/lib/relay.idl index a5ad34e0b5..21d3f58ea7 100644 --- a/lib/relay.idl +++ b/lib/relay.idl @@ -257,6 +257,31 @@ type AddFilesToTaskPayload { task: Task } +""" +Autogenerated input type of AddKeywordToTiplineMenu +""" +input AddKeywordToTiplineMenuInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + keyword: String! + language: String! + menu: String! + menuOptionIndex: Int! +} + +""" +Autogenerated return type of AddKeywordToTiplineMenu +""" +type AddKeywordToTiplineMenuPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + success: Boolean +} + type Annotation implements Node { annotated_id: String annotated_type: String @@ -8911,6 +8936,12 @@ type MutationType { """ input: AddFilesToTaskInput! ): AddFilesToTaskPayload + addNluKeywordToTiplineMenu( + """ + Parameters for AddKeywordToTiplineMenu + """ + input: AddKeywordToTiplineMenuInput! + ): AddKeywordToTiplineMenuPayload """ Allow multiple items to be marked as read or unread. @@ -9737,6 +9768,12 @@ type MutationType { """ input: RemoveFilesFromTaskInput! ): RemoveFilesFromTaskPayload + removeNluKeywordFromTiplineMenu( + """ + Parameters for RemoveKeywordFromTiplineMenu + """ + input: RemoveKeywordFromTiplineMenuInput! + ): RemoveKeywordFromTiplineMenuPayload replaceProjectMedia( """ Parameters for ReplaceProjectMedia @@ -11858,6 +11895,31 @@ type RemoveFilesFromTaskPayload { task: Task } +""" +Autogenerated input type of RemoveKeywordFromTiplineMenu +""" +input RemoveKeywordFromTiplineMenuInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + keyword: String! + language: String! + menu: String! + menuOptionIndex: Int! +} + +""" +Autogenerated return type of RemoveKeywordFromTiplineMenu +""" +type RemoveKeywordFromTiplineMenuPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + success: Boolean +} + """ Autogenerated input type of ReplaceProjectMedia """ diff --git a/public/relay.json b/public/relay.json index d10ba20745..4d93e551ed 100644 --- a/public/relay.json +++ b/public/relay.json @@ -1073,6 +1073,134 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "AddKeywordToTiplineMenuInput", + "description": "Autogenerated input type of AddKeywordToTiplineMenu", + "fields": null, + "inputFields": [ + { + "name": "language", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "keyword", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "menu", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "menuOptionIndex", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "AddKeywordToTiplineMenuPayload", + "description": "Autogenerated return type of AddKeywordToTiplineMenu", + "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": "success", + "description": null, + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "Annotation", @@ -48407,6 +48535,35 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "addNluKeywordToTiplineMenu", + "description": null, + "args": [ + { + "name": "input", + "description": "Parameters for AddKeywordToTiplineMenu", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "AddKeywordToTiplineMenuInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "AddKeywordToTiplineMenuPayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "bulkProjectMediaMarkRead", "description": "Allow multiple items to be marked as read or unread.", @@ -52380,6 +52537,35 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "removeNluKeywordFromTiplineMenu", + "description": null, + "args": [ + { + "name": "input", + "description": "Parameters for RemoveKeywordFromTiplineMenu", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "RemoveKeywordFromTiplineMenuInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "RemoveKeywordFromTiplineMenuPayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "replaceProjectMedia", "description": null, @@ -62364,6 +62550,134 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "RemoveKeywordFromTiplineMenuInput", + "description": "Autogenerated input type of RemoveKeywordFromTiplineMenu", + "fields": null, + "inputFields": [ + { + "name": "language", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "keyword", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "menu", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "menuOptionIndex", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "RemoveKeywordFromTiplineMenuPayload", + "description": "Autogenerated return type of RemoveKeywordFromTiplineMenu", + "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": "success", + "description": null, + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "ReplaceProjectMediaInput", diff --git a/test/controllers/graphql_controller_10_test.rb b/test/controllers/graphql_controller_10_test.rb index 4d256a7024..35c4c331fb 100644 --- a/test/controllers/graphql_controller_10_test.rb +++ b/test/controllers/graphql_controller_10_test.rb @@ -808,4 +808,70 @@ def setup assert_response :success assert !JSON.parse(@response.body)['data']['sendTiplineMessage']['success'] end + + test "should add NLU keyword to tipline menu option" do + SmoochNlu.any_instance.stubs(:enable!).once + SmoochNlu.any_instance.stubs(:add_keyword_to_menu_option).once + SmoochNlu.any_instance.stubs(:remove_keyword_from_menu_option).never + u = create_user is_admin: true + t = create_team + b = create_team_bot name: 'Smooch', login: 'smooch', set_approved: true + b.install_to!(t) + authenticate_with_user(u) + + query = "mutation { addNluKeywordToTiplineMenu(input: { language: \"en\", menu: \"main\", menuOptionIndex: 0, keyword: \"Foo bar\" }) { success } }" + post :create, params: { query: query, team: t.slug } + + assert_response :success + assert JSON.parse(@response.body)['data']['addNluKeywordToTiplineMenu']['success'] + end + + test "should remove NLU keyword from tipline menu option" do + SmoochNlu.any_instance.stubs(:enable!).once + SmoochNlu.any_instance.stubs(:add_keyword_to_menu_option).never + SmoochNlu.any_instance.stubs(:remove_keyword_from_menu_option).once + u = create_user is_admin: true + t = create_team + b = create_team_bot name: 'Smooch', login: 'smooch', set_approved: true + b.install_to!(t) + authenticate_with_user(u) + + query = "mutation { removeNluKeywordFromTiplineMenu(input: { language: \"en\", menu: \"main\", menuOptionIndex: 0, keyword: \"Foo bar\" }) { success } }" + post :create, params: { query: query, team: t.slug } + + assert_response :success + assert JSON.parse(@response.body)['data']['removeNluKeywordFromTiplineMenu']['success'] + end + + test "should not change tipline menu option NLU keywords if it's not a super-admin" do + SmoochNlu.any_instance.stubs(:enable!).never + SmoochNlu.any_instance.stubs(:add_keyword_to_menu_option).never + SmoochNlu.any_instance.stubs(:remove_keyword_from_menu_option).never + u = create_user is_admin: false + t = create_team + b = create_team_bot name: 'Smooch', login: 'smooch', set_approved: true + b.install_to!(t) + authenticate_with_user(u) + + query = "mutation { addNluKeywordToTiplineMenu(input: { language: \"en\", menu: \"main\", menuOptionIndex: 0, keyword: \"Foo bar\" }) { success } }" + post :create, params: { query: query, team: t.slug } + + assert_response :success + assert !JSON.parse(@response.body)['data']['addNluKeywordToTiplineMenu']['success'] + end + + test "should not change tipline menu option NLU keywords if tipline is not installed" do + SmoochNlu.any_instance.stubs(:enable!).never + SmoochNlu.any_instance.stubs(:add_keyword_to_menu_option).never + SmoochNlu.any_instance.stubs(:remove_keyword_from_menu_option).never + u = create_user is_admin: true + t = create_team + authenticate_with_user(u) + + query = "mutation { addNluKeywordToTiplineMenu(input: { language: \"en\", menu: \"main\", menuOptionIndex: 0, keyword: \"Foo bar\" }) { success } }" + post :create, params: { query: query, team: t.slug } + + assert_response :success + assert !JSON.parse(@response.body)['data']['addNluKeywordToTiplineMenu']['success'] + end end From f3eea086ea85ee36a1e3c757b3e9309db33bfb89 Mon Sep 17 00:00:00 2001 From: Caio <117518+caiosba@users.noreply.github.com> Date: Wed, 25 Oct 2023 20:34:07 -0300 Subject: [PATCH 2/4] Removing some "puts" --- app/graph/mutations/nlu_mutations.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/graph/mutations/nlu_mutations.rb b/app/graph/mutations/nlu_mutations.rb index 6bd6a436ba..3e2df6cb9c 100644 --- a/app/graph/mutations/nlu_mutations.rb +++ b/app/graph/mutations/nlu_mutations.rb @@ -14,14 +14,11 @@ def resolve(language:, menu:, menu_option_index:, keyword:) nlu.enable! nlu.add_keyword_to_menu_option(language, menu, menu_option_index, keyword) if toggle == :add nlu.remove_keyword_from_menu_option(language, menu, menu_option_index, keyword) if toggle == :remove - puts 'HERE 1' { success: true } else - puts 'HERE 2' { success: false } end rescue - puts 'HERE 3' { success: false } end end @@ -29,14 +26,12 @@ def resolve(language:, menu:, menu_option_index:, keyword:) class AddKeywordToTiplineMenu < ToggleKeywordInTiplineMenu def toggle - puts 'HERE 4' :add end end class RemoveKeywordFromTiplineMenu < ToggleKeywordInTiplineMenu def toggle - puts 'HERE 5' :remove end end From 6763cc498a9733a5425707f2289496ec9bdc961f Mon Sep 17 00:00:00 2001 From: Caio <117518+caiosba@users.noreply.github.com> Date: Wed, 25 Oct 2023 20:36:16 -0300 Subject: [PATCH 3/4] Making code more readable --- app/graph/mutations/nlu_mutations.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/graph/mutations/nlu_mutations.rb b/app/graph/mutations/nlu_mutations.rb index 3e2df6cb9c..0a98b8b239 100644 --- a/app/graph/mutations/nlu_mutations.rb +++ b/app/graph/mutations/nlu_mutations.rb @@ -12,8 +12,11 @@ def resolve(language:, menu:, menu_option_index:, keyword:) if User.current.is_admin nlu = SmoochNlu.new(Team.current.slug) nlu.enable! - nlu.add_keyword_to_menu_option(language, menu, menu_option_index, keyword) if toggle == :add - nlu.remove_keyword_from_menu_option(language, menu, menu_option_index, keyword) if toggle == :remove + if toggle == :add + nlu.add_keyword_to_menu_option(language, menu, menu_option_index, keyword) + elsif toggle == :remove + nlu.remove_keyword_from_menu_option(language, menu, menu_option_index, keyword) + end { success: true } else { success: false } From a217f7c2561ad5dd8c55bae1595cc1a32067adb6 Mon Sep 17 00:00:00 2001 From: Caio <117518+caiosba@users.noreply.github.com> Date: Wed, 25 Oct 2023 20:43:21 -0300 Subject: [PATCH 4/4] Removing local comment --- app/lib/smooch_nlu.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/lib/smooch_nlu.rb b/app/lib/smooch_nlu.rb index d0003a020d..7144e9cc73 100644 --- a/app/lib/smooch_nlu.rb +++ b/app/lib/smooch_nlu.rb @@ -5,9 +5,9 @@ class SmoochBotNotInstalledError < ::ArgumentError # FIXME: Make it more flexible # FIXME: Once we support paraphrase-multilingual-mpnet-base-v2 make it the only model used ALEGRE_MODELS_AND_THRESHOLDS = { - Bot::Alegre::ELASTICSEARCH_MODEL => 0.8 # , Sometimes this is easier for local development - # Bot::Alegre::OPENAI_ADA_MODEL => 0.8, - # Bot::Alegre::MEAN_TOKENS_MODEL => 0.6 + # Bot::Alegre::ELASTICSEARCH_MODEL => 0.8 # , Sometimes this is easier for local development + Bot::Alegre::OPENAI_ADA_MODEL => 0.8, + Bot::Alegre::MEAN_TOKENS_MODEL => 0.6 } include SmoochNluMenus