Skip to content

Commit

Permalink
Add 'mv' subcommand, use forked ruby-asana for development (#71)
Browse files Browse the repository at this point in the history
Adds a CLI 'mv' subcommand, which supports adding projects to tasks within the same workspace and changing their sections for now.

Move over to get_tasks() in ruby-asana, as get_tasks_for_section() lacks completed_since support.

Use forked version of ruby-asana gem for development use, as ruby-asana is pending key bugfixes for checkoff:

See

* Asana/ruby-asana#109
* Asana/ruby-asana#110
  • Loading branch information
apiology authored Jul 30, 2021
1 parent 854f0b3 commit fc1bced
Show file tree
Hide file tree
Showing 14 changed files with 861 additions and 142 deletions.
13 changes: 13 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,16 @@ source 'https://rubygems.org'

# Specify your gem's dependencies in checkoff.gemspec
gemspec

# ruby-asana gem is pending key bugfixes for checkoff as of
# 2021-07-29:
#
# See
# https://github.com/Asana/ruby-asana/issues/109
# https://github.com/Asana/ruby-asana/issues/110
#
gem 'asana',
git: 'https://github.com/apiology/ruby-asana',
branch: 'checkoff_fixes'

# gem 'asana', path: '/Users/broz/src/ruby-asana'
17 changes: 12 additions & 5 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
GIT
remote: https://github.com/apiology/ruby-asana
revision: 83732f040728a4ef1cfc9ed61f65878dbee6aa23
branch: checkoff_fixes
specs:
asana (0.10.4)
faraday (~> 1.0)
faraday_middleware (~> 1.0)
faraday_middleware-multi_json (~> 0.0)
oauth2 (~> 1.4)

PATH
remote: .
specs:
Expand All @@ -17,11 +28,6 @@ GEM
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
asana (0.10.3)
faraday (~> 1.0)
faraday_middleware (~> 1.0)
faraday_middleware-multi_json (~> 0.0)
oauth2 (~> 1.4)
ast (2.4.2)
bump (0.10.0)
cache (0.4.1)
Expand Down Expand Up @@ -125,6 +131,7 @@ PLATFORMS
x86_64-linux

DEPENDENCIES
asana!
bump
bundler
checkoff!
Expand Down
4 changes: 2 additions & 2 deletions coverage/.last_run.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"result": {
"line": 97.56,
"branch": 85.71
"line": 98.57,
"branch": 91.66
}
}
151 changes: 151 additions & 0 deletions lib/checkoff/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,112 @@
require_relative 'sections'

module Checkoff
# Move tasks from one place to another
class MvSubcommand
def validate_and_assign_from_location(from_workspace_arg, from_project_arg, from_section_arg)
if from_workspace_arg == :default_workspace
# Figure out what to do here - we accept a default
# workspace gid and default workspace_gid arguments elsewhere.
# however, there are undefaulted workspace_name arguments as
# well...
raise NotImplementedError, 'Not implemented: Teach me how to look up default workspace name'
end

@from_workspace_name = from_workspace_arg
@from_project_name = project_arg_to_name(from_project_arg)
@from_section_name = from_section_arg
end

def create_to_project_name(to_project_arg)
if to_project_arg == :source_project
from_project_name
else
project_arg_to_name(to_project_arg)
end
end

def create_to_section_name(to_section_arg)
if to_section_arg == :source_section
from_section_name
else
to_section_arg
end
end

def validate_and_assign_to_location(to_workspace_arg, to_project_arg, to_section_arg)
@to_workspace_name = to_workspace_arg
@to_workspace_name = from_workspace_name if to_workspace_arg == :source_workspace
@to_project_name = create_to_project_name(to_project_arg)
@to_section_name = create_to_section_name(to_section_arg)

return unless from_workspace_name != to_workspace_name

raise NotImplementedError, 'Not implemented: Teach me how to move tasks between workspaces'
end

def initialize(from_workspace_arg:,
from_project_arg:,
from_section_arg:,
to_workspace_arg:,
to_project_arg:,
to_section_arg:,
config: Checkoff::ConfigLoader.load(:asana),
projects: Checkoff::Projects.new(config: config),
sections: Checkoff::Sections.new(config: config),
logger: $stderr)
validate_and_assign_from_location(from_workspace_arg, from_project_arg, from_section_arg)
validate_and_assign_to_location(to_workspace_arg, to_project_arg, to_section_arg)

@projects = projects
@sections = sections
@logger = logger
end

def move_tasks(tasks, to_project, to_section)
tasks.each do |task|
# a. check if already in correct project and section (TODO)
# b. if not, put it there
@logger.puts "Moving #{task.name} to #{to_section.name}..."
task.add_project(project: to_project.gid, section: to_section.gid)
end
end

def fetch_tasks(from_workspace_name, from_project_name, from_section_name)
if from_section_name == :all_sections
raise NotImplementedError, 'Not implemented: Teach me how to move all sections of a project'
end

sections.tasks(from_workspace_name, from_project_name, from_section_name)
end

def run
# 0. Look up project and section gids
to_project = projects.project_or_raise(to_workspace_name, to_project_name)
to_section = sections.section_or_raise(to_workspace_name, to_project_name, to_section_name)

# 1. Get list of tasks which match
tasks = fetch_tasks(from_workspace_name, from_project_name, from_section_name)
# 2. for each task,
move_tasks(tasks, to_project, to_section)
# 3. tell the user we're done'
@logger.puts 'Done moving tasks'
end

private

attr_reader :from_workspace_name, :from_project_name, :from_section_name,
:to_workspace_name, :to_project_name, :to_section_name,
:projects, :sections

def project_arg_to_name(project_arg)
if project_arg.start_with? ':'
project_arg[1..].to_sym
else
project_arg
end
end
end

# CLI subcommand that shows tasks in JSON form
class ViewSubcommand
def initialize(workspace_name, project_name, section_name,
Expand Down Expand Up @@ -151,5 +257,50 @@ class CheckoffGLIApp
puts ViewSubcommand.new(workspace_name, project_name, section_name, task_name).run
end
end

desc 'Move tasks from one section to another within a project'

# rubocop:disable Metrics/BlockLength
command :mv do |c|
c.flag :from_workspace,
type: String,
default_value: :default_workspace,
desc: 'Workspace to move tasks from'
c.flag :from_project,
type: String,
required: true,
desc: 'Project to move tasks from'
c.flag :from_section,
type: String,
default_value: :all_sections,
desc: 'Section to move tasks from'
c.flag :to_workspace,
type: String,
default_value: :source_workspace,
desc: 'Workspace to move tasks to'
c.flag :to_project,
type: String,
default_value: :source_project,
desc: 'Section to move tasks to'
c.flag :to_section,
type: String,
default_value: :source_section,
desc: 'Section to move tasks to'
c.action do |_global_options, options, _args|
from_workspace = options.fetch('from_workspace')
from_project = options.fetch('from_project')
from_section = options.fetch('from_section')
to_workspace = options.fetch('to_workspace')
to_project = options.fetch('to_project')
to_section = options.fetch('to_section')
MvSubcommand.new(from_workspace_arg: from_workspace,
from_project_arg: from_project,
from_section_arg: from_section,
to_workspace_arg: to_workspace,
to_project_arg: to_project,
to_section_arg: to_section).run
end
end
# rubocop:enable Metrics/BlockLength
end
end
9 changes: 9 additions & 0 deletions lib/checkoff/projects.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ def project(workspace_name, project_name)
end
cache_method :project, LONG_CACHE_TIME

def project_or_raise(workspace_name, project_name)
project = project(workspace_name, project_name)
raise "Could not find project #{project_name} under workspace #{workspace_name}." if project.nil?

project
end
cache_method :project_or_raise, LONG_CACHE_TIME

# find uncompleted tasks in a list
def active_tasks(tasks)
tasks.select { |task| task.completed_at.nil? }
Expand Down Expand Up @@ -95,5 +103,6 @@ def my_tasks(workspace_name)
gid = result.gid
projects.find_by_id(gid)
end
cache_method :my_tasks, LONG_CACHE_TIME
end
end
30 changes: 14 additions & 16 deletions lib/checkoff/sections.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,10 @@ def tasks(workspace_name, project_name, section_name,
extra_fields: [])
section = section_or_raise(workspace_name, project_name, section_name)
options = projects.task_options
# asana-0.10.3 gem doesn't support per_page - not sure if API
# itself does
options.delete(:per_page)
options[:options][:fields] += extra_fields
options[:completed_since] = '9999-12-01' if only_uncompleted
client.tasks.get_tasks_for_section(section_gid: section.gid,
**options).to_a
client.tasks.get_tasks(section: section.gid,
**options)
end
cache_method :tasks, SHORT_CACHE_TIME

Expand All @@ -61,6 +58,18 @@ def section_task_names(workspace_name, project_name, section_name)
end
cache_method :section_task_names, SHORT_CACHE_TIME

def section_or_raise(workspace_name, project_name, section_name)
section = section(workspace_name, project_name, section_name)
if section.nil?
valid_sections = sections_or_raise(workspace_name, project_name).map(&:name)

raise "Could not find section #{section_name} under project #{project_name} " \
"under workspace #{workspace_name}. Valid sections: #{valid_sections}"
end
section
end
cache_method :section_or_raise, LONG_CACHE_TIME

private

# Given a project object, pull all tasks, then provide a Hash of
Expand Down Expand Up @@ -106,16 +115,5 @@ def section(workspace_name, project_name, section_name)
sections = sections_or_raise(workspace_name, project_name)
sections.find { |section| section.name.chomp(':') == section_name.chomp(':') }
end

def section_or_raise(workspace_name, project_name, section_name)
section = section(workspace_name, project_name, section_name)
if section.nil?
valid_sections = sections_or_raise(workspace_name, project_name).map(&:name)

raise "Could not find section #{section_name} under project #{project_name} " \
"under workspace #{workspace_name}. Valid sections: #{valid_sections}"
end
section
end
end
end
Loading

0 comments on commit fc1bced

Please sign in to comment.