Skip to content

Instantly share code, notes, and snippets.

@bsgreenb
Created August 28, 2019 23:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bsgreenb/e01bd470a7680b122fef85b40322f629 to your computer and use it in GitHub Desktop.
Save bsgreenb/e01bd470a7680b122fef85b40322f629 to your computer and use it in GitHub Desktop.
# For the actual business logic around syncing new events to asana
# TODO: optimize asana api calls
# TODO: replace logs with logger. per https://guides.rubyonrails.org/debugging_rails_applications.html
class Syncer
def initialize
@asana = AsanaAPI.new
@tag_parser = TagParser.new
end
def sync_projects
Rails.logger.info 'Syncing projects'
personal_projects = @asana.personal_projects
personal_projects.each do |personal_project|
sync_project(personal_project)
end
end
def sync_project(project)
project_name = project.name
Rails.logger.info "Syncing project #{project_name}"
project.tasks.each do |task|
sync_task(task.gid)
end
end
# Loop through the events, and sync the top level tasks
def sync_events(events)
Rails.logger.info 'Syncing events'
top_level_task_ids = []
events.each do |event|
sync_event(event)
end
end
def sync_event(event)
event = AsanaEvent.new(@asana, event) # this is ugly wrapper
return unless event.top_level_task_change?
sync_task(event.task_id)
end
# "Note that events are "skinny" - we expect consumers who desire syncing data to make
# additional calls to the API to retrieve the latest state. Because the data may have
# already changed by the time we send the event, it would be misleading to send a snapshot
# of the data along with the event." -- https://asana.com/developers/api-reference/webhooks
def sync_task(task_id)
task = @asana.find_task(task_id)
return unless valid_task?(task)
Rails.logger.info "Processing task #{task_id} with name '#{task.name}''"
task_updates, project_ids = get_task_updates_and_project_ids(task)
# TODO: move these to helpers on @asana, rather than in this class.
@asana.update_task(task, task_updates) if task_updates.any?
@asana.add_project_ids(task, project_ids) if project_ids.any?
end
def valid_task?(task)
if task.nil? # Missing Task Race condition handlin
Rails.logger.info "Skipping deleted task"
return false
end
unless task.parent.nil? # Gotta skip here after load despite earlier check
Rails.logger.info "Skipping subtask"
return false
end
if task.name.strip.empty?
Rails.logger.info "Skipping empty task #{task.gid}"
return false
end
true
end
# returns [task_updates, project_adds]
def get_task_updates_and_project_ids(task)
task_updates = {}
# Default to assigning
task_updates[:assignee] = @asana.user_id unless task.assignee
new_task_updates, project_ids = get_task_updates_and_project_ids_from_tags(task)
task_updates.merge!(new_task_updates)
[task_updates.compact, project_ids]
end
def get_task_updates_and_project_ids_from_tags(task)
task_updates = {}
project_ids = Set.new
processed_tags = Set.new
tags = @tag_parser.extract_unique_tags(task)
tags.each do |tag|
new_task_updates, project_id = get_task_updates_and_project_id_from_tag(task, tag)
task_updates.merge!(new_task_updates)
project_ids << project_id if project_id.present?
if task_updates.any? || project_id
processed_tags << tag
else
Rails.logger.warn "Could not process tag #{tag}"
end
end
# Remove processed_tags from
if processed_tags.any?
task_updates[:name] = @tag_parser.remove_tags(task.name, processed_tags)
# TODO: if it has no existing assignee_status but inbox or whatever, Then
# default to Upcoming
# TODO: if it has Upcoming but no due on, default to 2wks from now. When
# it has due date greater than today, but no assignee_status, set upcoming
end
[task_updates, project_ids.to_a]
end
def get_task_updates_and_project_id_from_tag(task, tag)
# Handle all tag processing with downcased.
tag = tag.downcase
task_updates = get_task_time_updates_from_tag(tag)
project_id = nil
if task_updates.empty?
# Could only be a project tag if not a time tag
project_id = find_matching_project_id(tag)
end
[task_updates, project_id]
end
# TODO: may want to add stuff like #May18 here later.
def get_task_time_updates_from_tag(tag)
task_updates = {}
if %w[today now].include?(tag)
task_updates[:due_on] = DateTimeUtils.date_format(Date.today)
task_updates[:assignee_status] = 'today'
elsif %w[1wk wk week 1week].include?(tag)
task_updates[:due_on] = DateTimeUtils.date_format(1.weeks.from_now.to_date)
task_updates[:assignee_status] = 'upcoming'
elsif %w[2wks 2weeks].include?(tag)
task_updates[:due_on] = DateTimeUtils.date_format(2.weeks.from_now.to_date)
task_updates[:assignee_status] = 'upcoming'
elsif %w[1month month 1mo].include?(tag)
task_updates[:due_on] = DatetimeUtils.date_format(1.month.from_now.to_date)
task_updates[:assignee_status] = 'upcoming'
elsif %w[tmrw tomorrow tommorrow tommorow].include?(tag)
task_updates[:due_on] = DateTimeUtils.date_format(Date.tomorrow)
task_updates[:assignee_status] = 'upcoming'
elsif tag == 'upcoming'
task_updates[:assignee_status] = 'upcoming'
elsif tag == 'later'
task_updates[:assignee_status] = 'later'
elsif DateTimeUtils.day_of_week?(tag)
task_updates[:due_on] = DateTimeUtils.date_of_next(tag)
task_updates[:assignee_status] = 'upcoming'
end
task_updates
end
# Takes a tag and finds first project with matching name
def find_matching_project_id(tag)
# TODO: implement this to match with contains.
project = @asana.personal_projects.find do |personal_project|
# Downcase and remove spaces for comparison to hash.
project_name = personal_project.name.downcase.delete(' ')
# Then check if it starts with the tag.
project_name.starts_with?(tag)
end
project ? project.gid : nil
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment