# frozen_string_literal: true require 'forwardable' require_relative 'projects' require_relative 'workspaces' require_relative 'clients' require_relative 'my_tasks' module Checkoff # Query different sections of Asana projects class Sections # @!parse # extend CacheMethod::ClassMethods MINUTE = 60 LONG_CACHE_TIME = MINUTE * 15 SHORT_CACHE_TIME = MINUTE * 5 extend Forwardable # @return [Checkoff::Projects] attr_reader :projects # @return [Checkoff::Workspaces] attr_reader :workspaces # @return [Class<Time>] attr_reader :time # @return [Checkoff::MyTasks] attr_reader :my_tasks # @param config [Hash<Symbol, Object>] # @param client [Asana::Client] # @param projects [Checkoff::Projects] # @param workspaces [Checkoff::Workspaces] # @param time [Class<Time>] def initialize(config: Checkoff::Internal::ConfigLoader.load(:asana), client: Checkoff::Clients.new(config: config).client, projects: Checkoff::Projects.new(config: config, client: client), workspaces: Checkoff::Workspaces.new(config: config, client: client), time: Time) @projects = projects @workspaces = workspaces @my_tasks = Checkoff::MyTasks.new(config: config, projects: projects, client: client) @client = client @time = time end # Returns a list of Asana API section objects for a given project # @param workspace_name [String] # @param project_name [String, Symbol] # # @return [Enumerable<Asana::Resources::Section>] def sections_or_raise(workspace_name, project_name) project = project_or_raise(workspace_name, project_name) # @sg-ignore client.sections.get_sections_for_project(project_gid: project.gid) end cache_method :sections_or_raise, SHORT_CACHE_TIME # Given a workspace name and project name, then provide a Hash of # tasks with section name -> task list of the uncompleted tasks # @param workspace_name [String] # @param project_name [String, Symbol] # @param extra_fields [Array<String>] # @return [Hash{[String, nil] => Enumerable<Asana::Resources::Task>}] def tasks_by_section(workspace_name, project_name, extra_fields: []) raise ArgumentError, 'Provided nil workspace name' if workspace_name.nil? raise ArgumentError, 'Provided nil project name' if project_name.nil? project = project_or_raise(workspace_name, project_name) if project_name == :my_tasks my_tasks.tasks_by_section_for_my_tasks(project, extra_fields: extra_fields) else tasks_by_section_for_project(project, extra_fields: extra_fields) end end # XXX: Rename to section_tasks # # Pulls task objects from a specified section # # @param workspace_name [String] # @param project_name [String, Symbol] # @param section_name [String, nil] # @param only_uncompleted [Boolean] # @param extra_fields [Array<String>] # # @return [Enumerable<Asana::Resources::Task>] def tasks(workspace_name, project_name, section_name, only_uncompleted: true, extra_fields: []) section = section_or_raise(workspace_name, project_name, section_name) options = projects.task_options options[:options][:fields] += extra_fields options[:completed_since] = '9999-12-01' if only_uncompleted client.tasks.get_tasks(section: section.gid, **options) end cache_method :tasks, SHORT_CACHE_TIME # Pulls just names of tasks from a given section. # @param workspace_name [String] # @param project_name [String, Symbol] # @param section_name [String, nil] # # @return [Array<String>] def section_task_names(workspace_name, project_name, section_name) task_array = tasks(workspace_name, project_name, section_name) task_array.map(&:name) end cache_method :section_task_names, SHORT_CACHE_TIME # @param workspace_name [String] # @param project_name [String, Symbol] # @param section_name [String, nil] # # @sg-ignore # @return [Asana::Resources::Section] def section_or_raise(workspace_name, project_name, section_name) s = section(workspace_name, project_name, section_name) if s.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 s end cache_method :section_or_raise, LONG_CACHE_TIME private # @return [Asana::Client] attr_reader :client # Given a project object, pull all tasks, then provide a Hash of # tasks with section name -> task list of the uncompleted tasks # @param project [Asana::Resources::Project] # @param extra_fields [Array<String>] # @return [Hash<[String,nil], Enumerable<Asana::Resources::Task>>] def tasks_by_section_for_project(project, extra_fields: []) raw_tasks = projects.tasks_from_project(project, extra_fields: extra_fields) active_tasks = projects.active_tasks(raw_tasks) by_section(active_tasks, project.gid) end # @param name [String] # @return [String, nil] def section_key(name) inbox_section_names = ['(no section)', 'Untitled section', 'Inbox', 'Recently assigned'] return nil if inbox_section_names.include?(name) name end # Given a list of tasks, pull a Hash of tasks with section name -> task list # @param tasks [Enumerable<Asana::Resources::Task>] # @param project_gid [String] # @return [Hash<[String,nil], Enumerable<Asana::Resources::Task>>] def by_section(tasks, project_gid) by_section = {} # @sg-ignore sections = client.sections.get_sections_for_project(project_gid: project_gid) sections.each { |section| by_section[section_key(section.name)] = [] } tasks.each { |task| file_task_by_section(by_section, task, project_gid) } by_section end cache_method :by_section, LONG_CACHE_TIME # @param by_section [Hash{[String, nil] => Enumerable<Asana::Resources::Task>}] # @param task [Asana::Resources::Task] # @param project_gid [String] # @return [void] def file_task_by_section(by_section, task, project_gid) # @type [Array<Hash>] membership = task.memberships.find { |m| m['project']['gid'] == project_gid } raise "Could not find task in project_gid #{project_gid}: #{task}" if membership.nil? # @type [String, nil] current_section = section_key(membership['section']['name']) # @sg-ignore by_section.fetch(current_section) << task end # @param workspace_name [String] # @param project_name [String, Symbol] # @return [Asana::Resources::Project] def project_or_raise(workspace_name, project_name) raise ArgumentError, 'Provide nil project_name' if project_name.nil? project = projects.project(workspace_name, project_name) if project.nil? raise "Could not find project #{project_name} " \ "under workspace #{workspace_name}" end project end # @sg-ignore # @param workspace_name [String] # @param project_name [String, Symbol] # @param section_name [String, nil] # @return [Asana::Resources::Section, nil] def section(workspace_name, project_name, section_name) sections = sections_or_raise(workspace_name, project_name) sections.find { |section| section_key(section.name)&.chomp(':') == section_name&.chomp(':') } end end end