Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite GoldMiner main entrypoint #43

Merged
merged 1 commit into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 22 additions & 16 deletions exe/gold_miner
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,32 @@
$LOAD_PATH.unshift("#{__dir__}/../lib")

require "gold_miner"
require "dotenv"

def debug(msg)
return if ENV["DEBUG"].nil?
def load_env_file
Dotenv.load!
Dry::Monads::Success()
rescue Errno::ENOENT
Dry::Monads::Failure("Could not load env file #{env_file.inspect}")
end

puts "[DEBUG] #{msg}"
def prepare
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A sub-goal of mine with this rewrite was to remove logic from this file. While I think the current approach is much cleaner than before, this method is still something I want to improve later.

load_env_file
.bind { GoldMiner::Slack::Client.build(api_token: ENV["SLACK_API_TOKEN"]) }
.fmap { |slack_client|
GoldMiner::SlackExplorer.new(slack_client, GoldMiner::AuthorConfig.default)
}
Comment on lines +17 to +20
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could have a SlackExplorer.build method that tries to by the client first and uses default values so we could rewrite this in a single line?

Suggested change
.bind { GoldMiner::Slack::Client.build(api_token: ENV["SLACK_API_TOKEN"]) }
.fmap { |slack_client|
GoldMiner::SlackExplorer.new(slack_client, GoldMiner::AuthorConfig.default)
}
.bind { GoldMiner::SlackExplorer.build }

end

channel = ARGV.first || "dev"

t0 = Time.now

GoldMiner
.mine_in(channel)
.fmap { |gold_container|
debug "Found #{gold_container.gold_nuggets.size} messages in #{Time.now - t0} seconds."

blog_post = GoldMiner.smith_blog_post(gold_container)
GoldMiner.distribute(blog_post)
}
prepare.bind { |slack_explorer|
GoldMiner
.new(
explorer: slack_explorer,
smith: GoldMiner::BlogPostSmith.new,
distributor: GoldMiner::TerminalDistributor.new
Comment on lines +28 to +30
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the previous refactor PRs had this in mind. Now that we can swap parts of the process and produce new behaviors.

When we have a proper CLI, this will be handy.

)
.mine(channel, start_on: GoldMiner::Helpers::Time.last_friday)
}
.or { |error| abort "[ERROR] #{error}" }

puts
debug "Done in #{Time.now - t0} seconds."
44 changes: 22 additions & 22 deletions lib/gold_miner.rb
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
# frozen_string_literal: true

require "dotenv"
require "dry/monads"
require "zeitwerk"
require "openai"

Zeitwerk::Loader.for_gem.setup

class GoldMiner
class << self
def mine_in(slack_channel, slack_client: GoldMiner::Slack::Client, env_file: ".env")
Dotenv.load!(env_file)
include Dry::Monads[:result]

prepare(slack_client)
.fmap { |client| explore(slack_channel, client) }
end
def initialize(explorer:, smith:, distributor:, env_file: ".env")
@explorer = explorer
@smith = smith
@distributor = distributor
@env_file = env_file
end

def smith_blog_post(gold_container, ...)
BlogPostSmith.new(...).smith(gold_container)
end
def mine(location, start_on:)
explore(location, start_on:)
.bind { |gold_container| smith(gold_container) }
.bind { |blog_post| distribute(blog_post) }
end

def distribute(blog_post)
TerminalDistributor.new.distribute(blog_post)
end
private

private
def explore(location, start_on:)
Success(@explorer.explore(location, start_on:))
end

def prepare(slack_client)
slack_client.build(api_token: ENV["SLACK_API_TOKEN"])
end
def smith(gold_container)
Success(@smith.smith(gold_container))
end

def explore(slack_channel, slack_client)
SlackExplorer
.new(slack_client, AuthorConfig.default)
.explore(slack_channel, start_on: Helpers::Time.last_friday)
end
def distribute(blog_post)
Success(@distributor.distribute(blog_post))
end
end
1 change: 0 additions & 1 deletion lib/gold_miner/slack/client.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# frozen_string_literal: true

require "dry/monads"
require "slack-ruby-client"

class GoldMiner
Expand Down
110 changes: 19 additions & 91 deletions spec/gold_miner_spec.rb
Original file line number Diff line number Diff line change
@@ -1,100 +1,28 @@
# frozen_string_literal: true

require "dry-monads"
require "dry/monads"
require "dotenv"

RSpec.describe GoldMiner do
include Dry::Monads[:result]

describe ".mine_in" do
it "loads the API token from the given env file" do
slack_client_builder = spy("Slack::Client builder")

GoldMiner.mine_in("dev", slack_client: slack_client_builder, env_file: "./spec/fixtures/.env.test")

expect(slack_client_builder).to have_received(:build).with(api_token: "test-token")
end

it "returns interesting messages from the given channel" do
message_author = TestFactories.create_slack_user
slack_message = TestFactories.create_slack_message(user: message_author)
search_result = [slack_message]

slack_client = instance_double(GoldMiner::Slack::Client, search_messages: search_result)
slack_client_builder = double(GoldMiner::Slack::Client, build: Success(slack_client))

result = GoldMiner.mine_in("dev", slack_client: slack_client_builder, env_file: "./spec/fixtures/.env.test")
gold_nuggets = result.value!.gold_nuggets

expect(gold_nuggets).to eq [
TestFactories.create_gold_nugget(
content: slack_message.text,
author: TestFactories.create_author(
id: message_author.username,
name: message_author.name,
link: "#to-do"
),
source: slack_message.permalink
)
]
end
end

describe ".smith_blog_post" do
it "creates a blog post from a gold container" do
date = "2022-10-07"
travel_to date do
with_env("OPEN_AI_API_TOKEN" => nil) do
channel = "dev"
gold_nuggets = [
TestFactories.create_gold_nugget,
TestFactories.create_gold_nugget
]
blog_post_class = spy("BlogPost class")
container = TestFactories.create_gold_container(
gold_nuggets: gold_nuggets,
origin: channel,
packing_date: Date.today
)
GoldMiner.smith_blog_post(container, blog_post_class:)

expect(blog_post_class).to have_received(:new).with(
slack_channel: channel,
gold_nuggets: gold_nuggets,
since: Date.parse(date),
writer: instance_of(GoldMiner::BlogPost::SimpleWriter)
)
end
end
end

context "when the OPEN_AI_API_TOKEN is set" do
it "uses the OpenAiWriter" do
date = "2022-10-07"
token = "test-token"
travel_to date do
with_env("OPEN_AI_API_TOKEN" => token) do
channel = "dev"
gold_nuggets = [
TestFactories.create_gold_nugget,
TestFactories.create_gold_nugget
]
blog_post_class = spy("BlogPost class")
container = TestFactories.create_gold_container(
gold_nuggets: gold_nuggets,
origin: channel,
packing_date: Date.today
)
GoldMiner.smith_blog_post(container, blog_post_class:)

expect(blog_post_class).to have_received(:new).with(
slack_channel: channel,
gold_nuggets: gold_nuggets,
since: Date.parse(date),
writer: instance_of(GoldMiner::BlogPost::OpenAiWriter)
)
end
end
end
describe "#mine" do
it "explores, smiths and distributes gold from the given location and date" do
location = "dev"
start_date = Date.parse("2023-10-20")
gold_container = TestFactories.create_gold_container
explorer = spy("Explorer", explore: gold_container)
blog_post = TestFactories.create_blog_post
smith = spy("Smith", smith: blog_post)
distributor = spy("Distributor", distribute: nil)

gold_miner = GoldMiner.new(explorer: explorer, smith: smith, distributor: distributor)
result = gold_miner.mine(location, start_on: start_date)

expect(explorer).to have_received(:explore).with(location, start_on: start_date)
expect(smith).to have_received(:smith).with(gold_container)
expect(distributor).to have_received(:distribute).with(blog_post)
expect(result).to be_success
end
end

Expand Down
10 changes: 10 additions & 0 deletions spec/support/test_factories.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ def create_gold_nugget(overriden_attributes = {})
GoldMiner::GoldNugget.new(**default_attributes.merge(overriden_attributes))
end

def create_blog_post(overriden_attributes = {})
default_attributes = {
slack_channel: "design",
gold_nuggets: [create_gold_nugget],
since: Date.today
}

GoldMiner::BlogPost.new(**default_attributes.merge(overriden_attributes))
end

def create_slack_user(overriden_attributes = {})
default_attributes = {
id: "U123",
Expand Down