Skip to content

Commit

Permalink
Added downloading data.
Browse files Browse the repository at this point in the history
  • Loading branch information
dblock committed Oct 27, 2024
1 parent b0db7b9 commit 6deaaf5
Show file tree
Hide file tree
Showing 30 changed files with 477 additions and 18 deletions.
41 changes: 28 additions & 13 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2024-10-27 18:33:30 UTC using RuboCop version 1.66.1.
# on 2024-10-27 20:54:43 UTC using RuboCop version 1.66.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
Expand Down Expand Up @@ -86,11 +86,11 @@ Naming/VariableNumber:
- 'spec/api/endpoints/sups_endpoint_spec.rb'
- 'spec/api/endpoints/users_endpoint_spec.rb'

# Offense count: 51
# Offense count: 58
RSpec/AnyInstance:
Enabled: false

# Offense count: 142
# Offense count: 155
# Configuration parameters: Prefixes, AllowedPatterns.
# Prefixes: when, with, without
RSpec/ContextWording:
Expand All @@ -111,7 +111,7 @@ RSpec/DescribedClass:
- 'spec/slack-sup/server_spec.rb'
- 'spec/slack-sup/service_spec.rb'

# Offense count: 62
# Offense count: 72
# Configuration parameters: CountAsOne.
RSpec/ExampleLength:
Max: 28
Expand Down Expand Up @@ -166,16 +166,31 @@ RSpec/LetSetup:
- 'spec/slack-sup/commands/unsubscribe_spec.rb'

# Offense count: 43
# Configuration parameters: .
# Configuration parameters: EnforcedStyle.
# SupportedStyles: have_received, receive
RSpec/MessageSpies:
EnforcedStyle: receive
Exclude:
- 'spec/api/endpoints/subscriptions_endpoint_spec.rb'
- 'spec/api/endpoints/teams_endpoint_spec.rb'
- 'spec/integration/subscribe_spec.rb'
- 'spec/integration/teams_spec.rb'
- 'spec/integration/update_cc_spec.rb'
- 'spec/models/round_spec.rb'
- 'spec/models/sup_spec.rb'
- 'spec/slack-sup/app_spec.rb'
- 'spec/slack-sup/commands/demote_spec.rb'
- 'spec/slack-sup/commands/opt_spec.rb'
- 'spec/slack-sup/commands/promote_spec.rb'
- 'spec/slack-sup/commands/set_spec.rb'
- 'spec/slack-sup/commands/subscription_spec.rb'
- 'spec/slack-sup/commands/unsubscribe_spec.rb'
- 'spec/slack-sup/server_spec.rb'

# Offense count: 146
# Offense count: 155
RSpec/MultipleExpectations:
Max: 10

# Offense count: 50
# Offense count: 52
# Configuration parameters: AllowSubject.
RSpec/MultipleMemoizedHelpers:
Max: 13
Expand All @@ -189,7 +204,7 @@ RSpec/NamedSubject:
- 'spec/api/endpoints/teams_endpoint_spec.rb'
- 'spec/slack-sup/app_spec.rb'

# Offense count: 66
# Offense count: 72
# Configuration parameters: AllowedGroups.
RSpec/NestedGroups:
Max: 6
Expand All @@ -200,7 +215,7 @@ RSpec/RepeatedDescription:
- 'spec/slack-sup/app_spec.rb'
- 'spec/slack-sup/commands/set_spec.rb'

# Offense count: 20
# Offense count: 21
# Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata.
# Include: **/*_spec.rb
RSpec/SpecFilePathFormat:
Expand Down Expand Up @@ -291,7 +306,7 @@ Style/NestedTernaryOperator:
Exclude:
- 'slack-sup/models/team.rb'

# Offense count: 12
# Offense count: 11
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns.
# SupportedStyles: predicate, comparison
Expand All @@ -301,7 +316,6 @@ Style/NumericPredicate:
- 'slack-sup/commands/rounds.rb'
- 'slack-sup/commands/stats.rb'
- 'slack-sup/models/round.rb'
- 'slack-sup/models/team.rb'

# Offense count: 3
# Configuration parameters: AllowedMethods.
Expand Down Expand Up @@ -333,7 +347,7 @@ Style/SlicingWithRange:
Exclude:
- 'slack-sup/api/helpers/sort_helpers.rb'

# Offense count: 13
# Offense count: 14
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: Mode.
Style/StringConcatenation:
Expand All @@ -343,4 +357,5 @@ Style/StringConcatenation:
- 'slack-sup/api/helpers/error_helpers.rb'
- 'slack-sup/app.rb'
- 'slack-sup/commands/rounds.rb'
- 'slack-sup/models/mixins/export.rb'
- 'slack-sup/models/team.rb'
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ source 'http://rubygems.org'
ruby '3.3.5'

gem 'ambit'
gem 'bytesize'
gem 'chronic'
gem 'dotiw'
gem 'grape'
Expand All @@ -15,6 +16,7 @@ gem 'newrelic_rpm'
gem 'rack', '~> 3.0.9'
gem 'rack-robotz'
gem 'rack-server-pages'
gem 'rubyzip'
gem 'slack-ruby-bot-server'
gem 'slack-ruby-bot-server-mailchimp'
gem 'slack-ruby-bot-server-rtm'
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ GEM
bigdecimal (3.1.8)
bson (5.0.1)
byebug (11.1.3)
bytesize (0.1.2)
capybara (3.40.0)
addressable
matrix
Expand Down Expand Up @@ -331,6 +332,7 @@ PLATFORMS
DEPENDENCIES
ambit
byebug
bytesize
capybara
chronic
database_cleaner-mongoid
Expand Down Expand Up @@ -359,6 +361,7 @@ DEPENDENCIES
rubocop-capybara
rubocop-rake
rubocop-rspec
rubyzip
selenium-webdriver
slack-ruby-bot-server
slack-ruby-bot-server-mailchimp
Expand Down
3 changes: 2 additions & 1 deletion config/initializers/slack-ruby-bot/hooks/message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ def command_classes
SlackSup::Commands::Next,
SlackSup::Commands::GCal,
SlackSup::Commands::Promote,
SlackSup::Commands::Demote
SlackSup::Commands::Demote,
SlackSup::Commands::Data
]
end
end
Expand Down
1 change: 1 addition & 0 deletions slack-sup/api/endpoints.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require 'slack-sup/api/endpoints/data_endpoint'
require 'slack-sup/api/endpoints/teams_endpoint'
require 'slack-sup/api/endpoints/users_endpoint'
require 'slack-sup/api/endpoints/rounds_endpoint'
Expand Down
35 changes: 35 additions & 0 deletions slack-sup/api/endpoints/data_endpoint.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module Api
module Endpoints
class DataEndpoint < Grape::API
format :binary
helpers Api::Helpers::AuthHelpers

namespace :data do
desc 'Get data.'
params do
requires :team_id, type: String, desc: 'Required team ID.'
end
get do
team = Team.find(_id: params[:team_id]) || error!('Team Not Found', 404)

authorize_short_lived_token! team

path = File.join(Dir.tmpdir, 'slack-sup2', team.id)
filename = team.export_filename(path)

if !File.exist?(filename) || (File.mtime(filename) + 1.hour < Time.now)
FileUtils.rm_rf(path)
FileUtils.makedirs(path)
Api::Middleware.logger.info "Generating data file for #{team}."
filename = team.export_zip!(path)
end

Api::Middleware.logger.info "Sending #{ByteSize.new(File.size(filename))} data file for #{team}."
content_type 'application/zip'
header['Content-Disposition'] = "attachment; filename=#{File.basename(filename)}"
File.binread filename
end
end
end
end
end
1 change: 1 addition & 0 deletions slack-sup/api/endpoints/root_endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class RootEndpoint < Grape::API
mount Api::Endpoints::SubscriptionsEndpoint
mount Api::Endpoints::CreditCardsEndpoint
mount Api::Endpoints::SlackEndpoint
mount Api::Endpoints::DataEndpoint

add_swagger_documentation
end
Expand Down
2 changes: 1 addition & 1 deletion slack-sup/api/helpers/auth_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Api
module Helpers
module AuthHelpers
def authorize_short_lived_token!(team)
jwt_token = headers['X-Access-Token']
jwt_token = headers['X-Access-Token'] || params['access_token']
error!('Access Denied', 401) unless team.short_lived_token_valid?(jwt_token)
end

Expand Down
7 changes: 7 additions & 0 deletions slack-sup/api/presenters/root_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ module RootPresenter
}
end

link :data do |opts|
{
href: "#{base_url(opts)}/api/data/{team_id}",
templated: true
}
end

%i[user team round sup].each do |model|
link model do |opts|
{
Expand Down
1 change: 1 addition & 0 deletions slack-sup/commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
require 'slack-sup/commands/gcal'
require 'slack-sup/commands/promote'
require 'slack-sup/commands/demote'
require 'slack-sup/commands/data'
35 changes: 35 additions & 0 deletions slack-sup/commands/data.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module SlackSup
module Commands
class Data < SlackRubyBot::Commands::Base
include SlackSup::Commands::Mixins::Subscribe

subscribe_command 'data' do |client, data, _match|
user = ::User.find_create_or_update_by_slack_id!(client, data.user)
raise SlackSup::Error, "Sorry, only #{user.team.team_admins_slack_mentions} can download data." unless user.team_admin?

dm = client.owner.slack_client.conversations_open(users: data.user)
client.owner.slack_client.chat_postMessage(
channel: dm.channel.id,
as_user: true,
text: 'Click here to download your team data.',
attachments: [
{
text: '',
attachment_type: 'default',
actions: [
{
type: 'button',
text: 'Download',
url: "#{SlackRubyBotServer::Service.url}/api/data?team_id=#{client.owner.id}&access_token=#{CGI.escape(client.owner.short_lived_token)}"
}
]
}
]
)

client.say(channel: data.channel, text: "Hey #{user.slack_mention}, check your DMs for a link.") unless data.channel[0] == 'D'
logger.info "DATA: #{data.team}, user=#{data.user}"
end
end
end
end
1 change: 1 addition & 0 deletions slack-sup/commands/help.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class Help < SlackRubyBot::Commands::Base
promote|demote [@mention] - promote or demote another user to/from admin
subscription - show team subscription info
unsubscribe - cancel auto-renew, unsubscribe
data - get a .zip of the team's data
More information at https://sup.playplay.io
```
Expand Down
4 changes: 4 additions & 0 deletions slack-sup/models.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
require 'csv'
require 'zip'

require 'slack-sup/models/mixins'
require 'slack-sup/models/error'
require 'slack-sup/models/team'
require 'slack-sup/models/user'
Expand Down
1 change: 1 addition & 0 deletions slack-sup/models/mixins.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require_relative 'mixins/export'
54 changes: 54 additions & 0 deletions slack-sup/models/mixins/export.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module SlackSup
module Models
module Mixins
module Export
extend ActiveSupport::Concern

def export_filename(root, name)
data_path = File.join(root, name)
File.join(data_path, "#{name}.zip")
end

def export_zip!(root, name)
data_path = File.join(root, name)
data_zip = File.join(data_path, "#{name}.zip")
export!(data_path)
Zip::File.open(data_zip, create: true) do |zipfile|
Dir.glob("#{data_path}/**/*").reject { |fn| File.directory?(fn) }.each do |file|
zipfile.add(file.sub(data_path + '/', ''), file)
end
end
data_zip
end

def export!(root, name = self.class.name, presenter = nil, coll = nil)
presenter ||= Object.const_get("Api::Presenters::#{self.class.name}Presenter")
keys = presenter.representable_attrs.keys - ['links']
data = coll || Array(self)
FileUtils.makedirs(root)
CSV.open(File.join(root, "#{name.downcase}.csv"), 'w', write_headers: true, headers: keys) do |csv|
data.each do |entry|
row = presenter.represent(entry).to_hash
row.merge!(row['_embedded']) if row.key?('_embedded')
csv << keys.map do |key|
value = row[key]
case value
when Hash
value.map do |k, v|
"#{k}=#{v}"
end.join("\n")
when Array
value.map do |v|
v["#{key.singularize}_name"] || v['user_name'] # HACK: assume user id
end.join("\n")
else
value
end
end
end
end
end
end
end
end
end
6 changes: 6 additions & 0 deletions slack-sup/models/round.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
class Round
include Mongoid::Document
include Mongoid::Timestamps
include SlackSup::Models::Mixins::Export

TIMEOUT = 60

Expand Down Expand Up @@ -97,6 +98,11 @@ def missed_users
team.users - paired_users
end

def export!(root)
super
super(root, 'sups', Api::Presenters::SupPresenter, sups)
end

private

def run!
Expand Down
1 change: 1 addition & 0 deletions slack-sup/models/round_stats.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class RoundStats
include ActiveModel::Model
include SlackSup::Models::Mixins::Export

attr_accessor :round, :sups_count, :users_in_sups_count, :outcomes

Expand Down
1 change: 1 addition & 0 deletions slack-sup/models/stats.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class Stats
include ActiveModel::Model
include SlackSup::Models::Mixins::Export

attr_accessor :rounds_count, :sups_count, :users_in_sups_count, :users_opted_in_count, :users_count, :outcomes, :team

Expand Down
1 change: 1 addition & 0 deletions slack-sup/models/sup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
class Sup
include Mongoid::Document
include Mongoid::Timestamps
include SlackSup::Models::Mixins::Export

field :outcome, type: String
field :channel_id, type: String
Expand Down
Loading

0 comments on commit 6deaaf5

Please sign in to comment.