Skip to content

Commit

Permalink
Add treatWordAsLuceneQuery to wordsSocialVolume (#4402)
Browse files Browse the repository at this point in the history
* Add treatWordAsLuceneQuery to wordsSocialVolume

* Add API tests for wordsSocialVolume
  • Loading branch information
IvanIvanoff authored Sep 30, 2024
1 parent b86ed13 commit 38b58dc
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 16 deletions.
15 changes: 5 additions & 10 deletions lib/sanbase/social_data/social_helper.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,12 @@ defmodule Sanbase.SocialData.SocialHelper do

def sources_total_string(), do: @sources |> Enum.join(",")

def replace_words_with_original_casing({:error, error}, _words), do: {:error, error}
def replace_words_with_original_casing(result, words) do
Enum.map(result, fn %{word: lowercase_word} = map ->
original_word = Enum.find(words, fn w -> String.downcase(w) == lowercase_word end)

def replace_words_with_original_casing({:ok, result}, words) do
result =
Enum.map(result, fn %{word: lowercase_word} = map ->
original_word = Enum.find(words, fn w -> String.downcase(w) == lowercase_word end)

Map.put(map, :word, original_word)
end)

{:ok, result}
Map.put(map, :word, original_word)
end)
end

def social_metrics_selector_handler(%{slug: slugs}) when is_list(slugs) do
Expand Down
27 changes: 23 additions & 4 deletions lib/sanbase/social_data/social_volume.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ defmodule Sanbase.SocialData.SocialVolume do
alias Sanbase.Project
alias Sanbase.Clickhouse.NftTrade

import Sanbase.Utils.Transform, only: [maybe_apply_function: 2]

defp http_client, do: Mockery.Macro.mockable(HTTPoison)

@recv_timeout 25_000
Expand Down Expand Up @@ -37,12 +39,30 @@ defmodule Sanbase.SocialData.SocialVolume do

def social_volume(%{words: words} = selector, from, to, interval, source, opts)
when is_list(words) do
transformed_words = Enum.reject(words, &(&1 == "***")) |> Enum.map(&String.downcase/1)
transformed_words = Enum.reject(words, &(&1 == "***"))
treat_word_as_lucene_query = Keyword.get(opts, :treat_word_as_lucene_query, false)

transformed_words =
case Keyword.get(opts, :treat_word_as_lucene_query, false) do
false -> transformed_words |> Enum.map(&String.downcase/1)
true -> transformed_words
end

selector = %{selector | words: transformed_words}

social_volume_list_request(selector, from, to, interval, source, opts)
|> handle_words_social_volume_response(selector)
|> Sanbase.SocialData.SocialHelper.replace_words_with_original_casing(words)
|> maybe_apply_function(fn result ->
if treat_word_as_lucene_query do
result
else
# If the words are **not** treated as lucene syntax (the default behaviour)
# they are lowercased before they are sent to metricshub. Because of that
# we need to properly map the result of metricshub back to the original casing
# of the word that came from the API call
SocialHelper.replace_words_with_original_casing(result, words)
end
end)
end

def social_volume(selector, from, to, interval, source, opts) do
Expand All @@ -51,8 +71,7 @@ defmodule Sanbase.SocialData.SocialVolume do
end

defp handle_response(response, selector) do
response
|> case do
case response do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
body
|> Jason.decode!()
Expand Down
8 changes: 6 additions & 2 deletions lib/sanbase_web/graphql/resolvers/social_data_resolver.ex
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,14 @@ defmodule SanbaseWeb.Graphql.Resolvers.SocialDataResolver do

def words_social_volume(
_root,
%{selector: %{words: _words} = selector, from: from, to: to, interval: interval},
%{selector: %{words: _words} = selector, from: from, to: to, interval: interval} = args,
_resolution
) do
SocialData.social_volume(selector, from, to, interval, :total)
treat_as_lucene = Map.get(args, :treat_word_as_lucene_query, false)

SocialData.social_volume(selector, from, to, interval, :total,
treat_word_as_lucene_query: treat_as_lucene
)
end

def words_social_dominance(
Expand Down
7 changes: 7 additions & 0 deletions lib/sanbase_web/graphql/schema/queries/social_data_queries.ex
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,13 @@ defmodule SanbaseWeb.Graphql.Schema.SocialDataQueries do
arg(:to, non_null(:datetime))
arg(:interval, :interval, default_value: "1d")

@desc ~s"""
If true, the word will be treated as a Lucene query. Default is false.
Lucene queries allows the word to be not a single word, but a query like: eth AND nft.
If false, the words are all lowercased and the AND/NOT/OR keywords meaning is lost.
"""
arg(:treat_word_as_lucene_query, :boolean, default_value: false)

complexity(&Complexity.from_to_interval/3)
middleware(AccessControl, %{allow_realtime_data: true})
cache_resolve(&SocialDataResolver.words_social_volume/3, ttl: 600, max_ttl_offset: 240)
Expand Down
174 changes: 174 additions & 0 deletions test/sanbase_web/graphql/social_data/words_social_volume_api_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
defmodule SanbaseWeb.Graphql.WordsSocialVolumeApiTest do
use SanbaseWeb.ConnCase, async: false

import Sanbase.Factory
import SanbaseWeb.Graphql.TestHelpers

setup do
%{user: user} = insert(:subscription_pro_sanbase, user: insert(:user))
conn = setup_jwt_auth(build_conn(), user)

%{conn: conn}
end

test "successfully fetch words social volume", context do
# This test does not include treatWordAsLuceneQuery: true, so the
# words are lowercased before being send and in the response
body =
%{
"data" => %{
"btc" => %{
"2024-09-28T00:00:00Z" => 373,
"2024-09-29T00:00:00Z" => 487,
"2024-09-30T00:00:00Z" => 323
},
"eth or nft" => %{
"2024-09-28T00:00:00Z" => 1681,
"2024-09-29T00:00:00Z" => 3246,
"2024-09-30T00:00:00Z" => 1577
}
}
}
|> Jason.encode!()

resp = %HTTPoison.Response{status_code: 200, body: body}

Sanbase.Mock.prepare_mock(HTTPoison, :get, fn _url, _headers, options ->
search_texts =
Map.new(options[:params])
|> Map.get("search_texts")

# Assert that the words are lowercased before they are sent
assert search_texts == "eth or nft,btc"

{:ok, resp}
end)
|> Sanbase.Mock.run_with_mocks(fn ->
query = """
{
wordsSocialVolume(
selector: { words: ["eth OR nft", "btc"] }
from: "2024-09-28T00:00:00Z"
to: "2024-09-30T00:00:00Z"
interval: "1d"
){
word
timeseriesData{
datetime
mentionsCount
}
}
}
"""

result =
context.conn
|> post("/graphql", query_skeleton(query))
|> json_response(200)

assert result == %{
"data" => %{
"wordsSocialVolume" => [
%{
"timeseriesData" => [
%{"datetime" => "2024-09-28T00:00:00Z", "mentionsCount" => 373},
%{"datetime" => "2024-09-29T00:00:00Z", "mentionsCount" => 487},
%{"datetime" => "2024-09-30T00:00:00Z", "mentionsCount" => 323}
],
"word" => "btc"
},
%{
"timeseriesData" => [
%{"datetime" => "2024-09-28T00:00:00Z", "mentionsCount" => 1681},
%{"datetime" => "2024-09-29T00:00:00Z", "mentionsCount" => 3246},
%{"datetime" => "2024-09-30T00:00:00Z", "mentionsCount" => 1577}
],
"word" => "eth OR nft"
}
]
}
}
end)
end

test "successfully fetch words social volume - treatWordAsLuceneQuery: true", context do
# This test does not include treatWordAsLuceneQuery: true, so the
# words are lowercased before being send and in the response
body =
%{
"data" => %{
"BTC" => %{
"2024-09-28T00:00:00Z" => 373,
"2024-09-29T00:00:00Z" => 487,
"2024-09-30T00:00:00Z" => 323
},
"eth OR nft" => %{
"2024-09-28T00:00:00Z" => 1681,
"2024-09-29T00:00:00Z" => 3246,
"2024-09-30T00:00:00Z" => 1577
}
}
}
|> Jason.encode!()

resp = %HTTPoison.Response{status_code: 200, body: body}

Sanbase.Mock.prepare_mock(HTTPoison, :get, fn _url, _headers, options ->
search_texts =
Map.new(options[:params])
|> Map.get("search_texts")

# Assert that the words are **not** lowercased before they are sent
assert search_texts == "eth OR nft,BTC"

{:ok, resp}
end)
|> Sanbase.Mock.run_with_mocks(fn ->
query = """
{
wordsSocialVolume(
selector: { words: ["eth OR nft", "BTC"] }
from: "2024-09-28T00:00:00Z"
to: "2024-09-30T00:00:00Z"
interval: "1d"
treatWordAsLuceneQuery: true
){
word
timeseriesData{
datetime
mentionsCount
}
}
}
"""

result =
context.conn
|> post("/graphql", query_skeleton(query))
|> json_response(200)

assert result == %{
"data" => %{
"wordsSocialVolume" => [
%{
"timeseriesData" => [
%{"datetime" => "2024-09-28T00:00:00Z", "mentionsCount" => 373},
%{"datetime" => "2024-09-29T00:00:00Z", "mentionsCount" => 487},
%{"datetime" => "2024-09-30T00:00:00Z", "mentionsCount" => 323}
],
"word" => "BTC"
},
%{
"timeseriesData" => [
%{"datetime" => "2024-09-28T00:00:00Z", "mentionsCount" => 1681},
%{"datetime" => "2024-09-29T00:00:00Z", "mentionsCount" => 3246},
%{"datetime" => "2024-09-30T00:00:00Z", "mentionsCount" => 1577}
],
"word" => "eth OR nft"
}
]
}
}
end)
end
end

0 comments on commit 38b58dc

Please sign in to comment.