Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c4dc49a
update test_helper for VCR
kanderson38 Mar 19, 2019
5152b71
Created lib and spac files.
bonara Mar 19, 2019
9098d75
successful call to users api
kanderson38 Mar 19, 2019
6f06b65
Added channel class and self.list method
bonara Mar 19, 2019
a9ca644
create list of users
kanderson38 Mar 19, 2019
ca2213c
Added self.list method for channel.rb
bonara Mar 19, 2019
ab56d8a
Added tests to channel_spec.rb
bonara Mar 19, 2019
c8ae6e9
added user_spec
kanderson38 Mar 19, 2019
2cf0c05
handled !200 errors
kanderson38 Mar 19, 2019
8baa484
Updated slack.rb to get user input
bonara Mar 19, 2019
a431470
list_user_details
kanderson38 Mar 19, 2019
6d54995
added channels print_details method
bonara Mar 19, 2019
f8fc318
added workspace init tests
kanderson38 Mar 19, 2019
9f3bb6d
select_user method
kanderson38 Mar 20, 2019
eac545e
Added select_channel method and test
bonara Mar 20, 2019
fd0bb90
Added show details, select user and channel methods and tests.
bonara Mar 20, 2019
9f1b726
updated slack.rb to include new menu items
kanderson38 Mar 20, 2019
1fc9558
Added send message to recipient and worskpace.
bonara Mar 20, 2019
7136fec
updated CLI and fixed print_details in workspace
kanderson38 Mar 21, 2019
d076c81
Added a test to handle empty messages.
bonara Mar 21, 2019
c687709
Added error message if invalid user or channel selected
kanderson38 Mar 21, 2019
abb531d
Corrected a typo in the test
bonara Mar 22, 2019
bfc5486
started bot-settings code
kanderson38 Mar 22, 2019
c496d3a
set new bot-settings and write to json file
kanderson38 Mar 22, 2019
f1f47a5
added conditions to check if recipient is user when posting.
bonara Mar 22, 2019
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ build/
build-iPhoneOS/
build-iPhoneSimulator/


## Specific to RubyMotion (use of CocoaPods):
#
# We recommend against adding the Pods directory to your .gitignore. However
Expand Down
Empty file removed lib/.keep
Empty file.
1 change: 1 addition & 0 deletions lib/bot-settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"username":"Turtle","icon_emoji":":turtle:"}
40 changes: 40 additions & 0 deletions lib/channel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require "pry"

require_relative "recipient"

class Channel < Recipient
attr_reader :topic, :member_count

def initialize(slack_id, name, topic, member_count)
super(slack_id, name)
@topic = topic
@member_count = member_count
end

def details
return "#{name} #{topic} member count: #{member_count} slack id: #{slack_id}"
end

def self.list
raw_data = self.get("channel")

unless raw_data.code == 200
raise SlackApiError, "Improper request: #{raw_data.message}"
end
channel_list = []
channels = raw_data["channels"]
channels.each do |channel|
slack_id = channel["id"]
name = channel["name"]
topic = channel["topic"]["value"]
member_count = channel["members"].count

new_channel = Channel.new(slack_id, name, topic, member_count)
channel_list << new_channel
end
return channel_list
end
end

# binding.pry
# self.list
67 changes: 67 additions & 0 deletions lib/recipient.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
require "dotenv"
require "httparty"
require "json"
Dotenv.load

class Recipient
class SlackApiError < StandardError; end

attr_accessor :slack_id, :name

def initialize(slack_id, name)
@slack_id = slack_id
@name = name
end

CHANNEL_URL = "https://slack.com/api/channels.list"
USER_URL = "https://slack.com/api/users.list"
POST_URL = "https://slack.com/api/chat.postMessage"

Choose a reason for hiding this comment

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

URL constants! 🎉

Though, I think these probably wanted to live in Workspace.

def self.get(type)

Choose a reason for hiding this comment

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

self.get should take url and params not just a type. Keeping track of what to pass in should be handled by Workspace.

params = {
"token" => ENV["SLACK_API_TOKEN"],
}
if type == "user"
url = USER_URL
elsif type == "channel"
url = CHANNEL_URL
end

response = HTTParty.get(url, query: params)

Choose a reason for hiding this comment

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

Forgot to check for errors.

end

def send_msg(message, recipient)
# settings = {}
file = File.read("lib/bot-settings.json")
settings = JSON.parse(file)
params = {
"token" => ENV["SLACK_API_TOKEN"],
"channel" => recipient.slack_id,
"text" => message,
"username" => settings["username"],
"icon_emoji" => settings["icon_emoji"],
}
if recipient.class == User
params["as_user"] = true

Choose a reason for hiding this comment

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

as_user isn't for if the target is a user, it lets you send things as though you are the user whose token you are making the call with.

end

response = HTTParty.post(
POST_URL,
body: params,
headers: { "Content-Type" => "application/x-www-form-urlencoded" },
)
unless response.code == 200 && response.parsed_response["ok"]
raise SlackApiError, "Error: #{response.parsed_response["error"]}"
end
return response
end

private

def details
raise NotImplementedError, "Implement this in the child class."
end

def self.list
raise NotImplementedError, "Implement this in the child class."
end
end
53 changes: 51 additions & 2 deletions lib/slack.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,60 @@
#!/usr/bin/env ruby
require_relative "workspace"

def main
puts "Welcome to the Ada Slack CLI!"

# TODO project
workspace = Workspace.new
input = ""
while input != "quit"
puts "Choose an option: \n list users \n list channels \n \n select user \n select channel \n details \n \n send message \n \n change settings \n quit"
input = gets.chomp
case input
when "list users"
puts workspace.print_details("users")
when "list channels"
puts workspace.print_details("channels")
when "select user"
print "Enter the user name or Slack ID: "
input_user = gets.chomp
workspace.select_user(input_user)
if workspace.selected == nil
puts "User not found"
end
when "select channel"
print "Enter the channel name or Slack ID: "
input_channel = gets.chomp
workspace.select_channel(input_channel)
if workspace.selected == nil
puts "Channel not found"
end
when "details"
if workspace.selected == nil
puts "Please select a user or channel as a recipient"
else
puts workspace.show_details
end
when "send message"
if workspace.selected == nil
puts "Please select a user or channel as a recipient"
else
print "Enter your message: "
text = gets.chomp
workspace.send_message(text)
end
when "change settings"
print "Enter new username: "
name = gets.chomp
print "Enter new emoji: "
emoji = gets.chomp
workspace.update_settings(name, emoji)
when "quit"
else
puts "Please select one of the options"
end
end

puts "Thank you for using the Ada Slack CLI"
end

main if __FILE__ == $PROGRAM_NAME
main if __FILE__ == $PROGRAM_NAME
43 changes: 43 additions & 0 deletions lib/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
require "pry"

require_relative "recipient"

class User < Recipient
attr_reader :real_name, :status_text, :status_emoji

def initialize(slack_id, name, real_name, status_text, status_emoji)
super(slack_id, name)
@real_name = real_name
@status_text = status_text
@status_emoji = status_emoji
end

def details
return "#{name} (#{real_name}) slack id: #{slack_id}"
end

def self.list
raw_data = self.get("user")

unless raw_data.code == 200

Choose a reason for hiding this comment

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

Forgot to check for the parsed out ok.

raise SlackApiError, "Improper request: #{raw_data.message}"
end

user_list = []
members = raw_data["members"]
members.each do |member|
slack_id = member["id"]
name = member["name"]
real_name = member["real_name"]
status_text = member["status_text"]
status_emoji = member["status_emoji"]

user = User.new(slack_id, name, real_name, status_text, status_emoji)
user_list << user
end
return user_list
end
end

# binding.pry
# self.list
63 changes: 63 additions & 0 deletions lib/workspace.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
require "pry"
require "json"

require_relative "user"
require_relative "channel"

class Workspace
attr_reader :users, :channels, :selected

def initialize
@users = User.list
@channels = Channel.list
@selected = nil
end

def select_channel(user_input)
selected = channels.select do |channel|
channel.name == user_input || channel.slack_id == user_input
end
@selected = selected.first

Choose a reason for hiding this comment

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

Ruby's .find enumerable method might be helpful to clarify this code.

end

def select_user(user_input)
selected = users.select do |user|
user.name == user_input || user.slack_id == user_input
end
@selected = selected.first

Choose a reason for hiding this comment

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

Same here.

end

def show_details
return @selected.details
end

def print_details(recipients)
if recipients == "users"

Choose a reason for hiding this comment

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

I'd like to see recipients just be an array of objects, but if you want it to be something you switch on like this you should use :users instead of "users" since generally we want to use atoms when we match on things like this.

return_array = []
users.each do |user|
return_array << user.details
end
elsif recipients == "channels"
return_array = []
channels.each do |channel|
return_array << channel.details
end
end
return return_array
end

def update_settings(name, emoji)

Choose a reason for hiding this comment

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

I like that your configuration system is persistent and uses JSON. 😃

json_hash = { "username" => name,
"icon_emoji" => emoji }

File.open("lib/bot-settings.json", "w") do |f|
f.write(json_hash.to_json)
end
end

def send_message(text)
selected.send_msg(text, selected)
end
end

# binding.pry
15 changes: 15 additions & 0 deletions specs/channel_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require_relative "test_helper"

describe "Channel class" do

Choose a reason for hiding this comment

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

I would have liked to see some edge case tests on Channel. What happens if you try to get a channel that doesn't exist, etc?

describe "self.list" do
it "can return all channels" do
VCR.use_cassette("slack_channel") do
response = Channel.get("channel")

expect(response["channels"]).wont_be_nil
expect(response["channels"].first["name"]).must_equal "random"
expect(response["channels"].first["members"].count).must_equal 2
end
end
end
end
1 change: 1 addition & 0 deletions specs/recipient_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require "test_helper"

Choose a reason for hiding this comment

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

This should be require_relative, not require. Using require here breaks rake (but not guard).

Suggested change
require "test_helper"
require_relative "test_helper"

Choose a reason for hiding this comment

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

Also, while it's hard to test an abstract class it would be good to at a minimum make sure that your template methods raise NotImplementedError.

34 changes: 25 additions & 9 deletions specs/test_helper.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
require 'simplecov'
require "simplecov"
SimpleCov.start

require 'minitest'
require 'minitest/autorun'
require 'minitest/reporters'
require 'minitest/skip_dsl'
require 'vcr'
require "minitest"
require "minitest/autorun"
require "minitest/reporters"
require "minitest/skip_dsl"
require "vcr"

require "dotenv"
Dotenv.load

require_relative '../lib/channel.rb'
require_relative '../lib/user.rb'
require_relative '../lib/recipient.rb'
require_relative '../lib/workspace.rb'

Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new

VCR.configure do |config|
config.cassette_library_dir = "specs/cassettes"
config.hook_into :webmock
end
config.cassette_library_dir = "specs/cassettes" # folder where casettes will be located
config.hook_into :webmock # tie into this other tool called webmock
config.default_cassette_options = {
:record => :new_episodes, # record new data when we don't have it yet
:match_requests_on => [:method, :uri, :body], # The http method, URI and body of a request all need to match
}
# Don't leave our token lying around in a cassette file.
config.filter_sensitive_data("<SLACK_API_TOKEN>") do
ENV["SLACK_API_TOKEN"]

Choose a reason for hiding this comment

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

🎉

end
end
22 changes: 22 additions & 0 deletions specs/user_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require_relative "test_helper"

describe "User class" do
describe "self.list" do
it "can return all users" do
VCR.use_cassette("slack_user") do
response = User.get("user")

expect(response["members"]).wont_be_nil
expect(response["members"].first["name"]).must_equal "slackbot"
expect(response["members"].first["id"]).must_equal "USLACKBOT"
end
end

# it "raises an exception if response is invalid" do
# VCR.use_cassette("slack_user") do
# response = User.get("user")
# expect(response["ok"]).must_equal false
# end
# end

Choose a reason for hiding this comment

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

My guess is you couldn't get this functionality working in time for submission.

It was a good test though and would have caught an issue. 👍🏼

end
end
Loading