flavorjones / calendar-assistant Goto Github PK
View Code? Open in Web Editor NEWCommand-line tool to manage your Google Calendar
License: Apache License 2.0
Command-line tool to manage your Google Calendar
License: Apache License 2.0
ca join --debug
/Users/user/.rvm/rubies/ruby-2.4.1/lib/ruby/2.4.0/uri/rfc2396_parser.rb:268:in `extract': undefined method `scan' for nil:NilClass (NoMethodError)
Zoom could be in the location then description then it should fail gracefully.
That is, if the event is anything except "calendar default"
(I'm not totally sure this is how the API works, we may need to do some exploration.)
I'm imagining a way to configure an association of a color with a meeting type:
style:
- meeting_type: 1-1
color: basil
- meeting_type: self
color: sage
so that when meetings are created/rescheduled/etc. the color is set appropriately
see branch flavorjones-new-pr-resource
in which I tried to do too much.
ref
error from the previous resource inputI'd like an easy command to reschedule a meeting for a time when all invitees are available.
This requires some thought around how to identify a calendar event. There's a unique id assigned by GCal, but it's not human-readable. For the purposes of a functional story we'll use this calendar-id.
Some options and inputs for this command:
Some heuristics and constraints:
(This could and should probably be broken out into multiple stories.)
My dream for a utility such as this is to generate a bunch of "busy" meetings of varying lengths to fill a block of time rather than using a single multi-hour meeting block.
My experience is that folks trying to find a time on your calendar a more inclined to double book you over a long meeting block than if it looks like you have multiple meetings.
something like:
calendar-assistant busy-block create <profile-name> [<range datespec>]
Generates something like this:
Events that have a room attached often look like this:
...
@attendees=
[#<Google::Apis::CalendarV3::EventAttendee
@display_name="redacted",
@email="[email protected]",
@organizer=true,
@response_status="accepted",
@self=true>,
#<Google::Apis::CalendarV3::EventAttendee
@display_name="redacted",
@email="[email protected]",
@response_status="accepted">,
#<Google::Apis::CalendarV3::EventAttendee
@display_name="LDN - Bartik (4 people, lounge)",
@email=
"[email protected]",
@resource=true,
@response_status="accepted">],
...
These events should still be considered 1:1s because the third attendee is the room resource.
Currently "setup" interaction code is embedded in cli.rb
And "authorize" interaction code is embedded in authorizer.rb
They're both probably wrong:
Out#puts
and #prompt
I think this probably means introducing a new class of some kind to do this work. Or maybe just jam more methods into CLIHelpers.
I'd really like to be able to analyze where I'm spending my time, ideally this is a backwards-looking stat but might be applied forwards.
Some rough thoughts on buckets that would be useful for me:
Potential UX:
$ calendar-assistant stats [email protected] - looking back one month Time spent in recurring meetings: 10h 30m (20.2%) Time spent in one-on-ones: 22h 10m (41.6%) Unstructured time: 18h 30m (30.3%)
This could support the -a
option to run against someone else to whose calendar you have access.
This should support a time range as well (default to past month).
Something interesting to think about: should percentages be calculated from the user's business day (config) or be calculated from total time spent in meetings?
Because BusinessTime
does all its cool shit on the Time
class, having DateTime
objects floating around is either a) asking for trouble, or b) requires invoking #to_time
all over the place.
Instead let's make sure Event
does the right thing by casting the return values as Time
for:
#start_time
#end_time
and let's make sure all internal methods use the #start_time
and #end_time
methods:
#past?
#future?
#duration
Google generally automatically adds events when you fly places, it reads your itinerary emails (nbd).
Wouldn't it be cool if there was an automatic way to generate location events based on these automatic flying events?
# create an event titled `๐บ Columbus` on the days of the trip to Columbus
$ calendar-assistant location list flights
[1] 2018-09-24 - 2018-09-27: New York => Columbus
[2] 2018-10-01 - 2018-10-10: New York => San Francisco
$ calendar-assistant location set-flight 1
Created:
2018-09-24 - 2018-09-27 | ๐บ Columbus (not-busy, self)
Or, something
When I'm trying to schedule a meeting with someone outside the org, I often provide a list of upcoming available slots. Ultimately, I'd love to have Sunrise Meet back. In the short term, my suggestion would be something like:
$ calendar-assistant show availability work [--size 60min]
Monday (10/8) 2pm-3:30pm
Tuesday (10/9) 11am-12pm
Wednesday (10/10) 4pm-6pm
Create file based EventRepository
Resolution means accept/decline/reschedule
For example:
ca avail monday..next week friday -a [email protected],[email protected] -l 60m [email protected], [email protected] - looking for blocks at least 1 hr long - between 9am and 6pm in America/Los_Angeles - between 9am and 6pm in America/New_York Availability on Monday, December 3: โข 10:30am - 11:30am PST / 1:30pm - 2:30pm EST (1h) Availability on Tuesday, December 4: โข 11:15am - 11:30am PST / 2:15pm - 2:30pm EST (15m) โข 1:30pm - 1:45pm PST / 4:30pm - 4:45pm EST (15m) Availability on Wednesday, December 5: (No available blocks in this time range.) Availability on Thursday, December 6: (No available blocks in this time range.) Availability on Friday, December 7: โข 2:00pm - 3:00pm PST / 5:00pm - 6:00pm EST (1h)
I'd like to have my location events default to 'public' instead of 'default visibility', but others probably have other opinions.
Rather than default to "public", I'd like a commandline parameter to set them public at create time.
This might even be a good excuse to introduce a user config to set this default.
hanging off #18
I'm imagining a workflow like:
Given [email protected] whose home time zone is America/Los_Angeles ...
$ calendar-assistant avail -r me,[email protected] [email protected], [email protected] - looking for blocks at least 30 mins long - between 9am and 6pm in America/New_York - between 9am and 6pm in America/Los_Angeles Availability on Monday, November 19: โข 12:00pm - 1:00pm EST / 9:00am - 10:00am PST (1h) โข 1:30pm - 2:00pm EST / 10:30am - 11:00am PST (30m)
The alternative UX would be repeating usage of params:
calendar-assistant avail -r me -r [email protected]
which is something that Thor doesn't support (it instead prefers passing multiple space-delimited options like -r me [email protected]
which I find confusing and I'm pretty sure isn't POSIX-compliant ๐คทโโ๏ธ).
I'm open to other ideas here. @gsiener @mikfreedman
The web UI does a thing where it shows when the other person has declined.
I'd like linting to prompt me to decline or reschedule.
An imaginary journey:
$ calendar-assistant lint today
Found meetings that were declined:
2018-09-17 08:00 - 09:00 | ๐ซ MD / RDZ (1:1, recurring)
Would you like to reschedule? (y/n): n
(Declining "๐ซ MD / RDZ")
2018-09-17 14:00 - 14:30 | ๐ซ Evan / MikeD (1:1, recurring)
Would you like to reschedule? (y/n): y
(Rescheduling "๐ซ Evan / MikeD")
... eliding this input because it's another story ...
(Depends on #7 for rescheduling functionality.)
Found a few issues
event.accepted?
which doesn't work for private events (events on other people's calendars that I can't see details for). I think we can work around this by using event.private? || event.accepted?
"This is an out-of-office event, which can only be edited in Google Calendar. Meetings during this time will be automatically declined."
$ ruby -Ilib bin/calendar-assistant show yesterday -r [email protected]
[email protected] (all times in America/Los_Angeles)
2018-11-16 09:06 - 09:20 | redacted (awaiting, recurring)
2018-11-16 10:00 - 10:30 | redacted (1:1, recurring)
2018-11-16 10:30 - 11:00 | redacted (awaiting, recurring)
2018-11-16 11:00 - 12:00 | redacted (1:1, recurring)
2018-11-16 11:00 - 11:30 | redacted (self)
2018-11-16 12:00 - 12:30 | redacted (awaiting, recurring)
2018-11-16 12:30 - 13:29 | REDACTED (recurring, self)
2018-11-16 12:30 - 18:00 | redacted (self)
You can see that there are two meetings at 11, one ends at 11:30 and one ends at 12:00. The Scheduler gets confused by this and thinks the person is free from 11:30-12:00:
$ ruby -Ilib bin/calendar-assistant avail yesterday -r [email protected]
[email protected]
- looking for blocks at least 30 mins long
- between 9am and 6pm in America/Los_Angeles
Availability on Friday, November 16:
โข 9:00am - 10:00am PST (1h)
โข 10:30am - 11:00am PST (30m)
โข 11:30am - 12:30pm PST (1h)
$ ruby -Ilib bin/calendar-assistant show yesterday -r [email protected]
[email protected] (all times in America/Chicago)
2018-11-16 09:00 - 09:30 | (private) (private)
2018-11-16 10:00 - 10:30 | (private) (private)
2018-11-16 12:00 - 12:30 | (private) (private)
2018-11-16 13:30 - 15:00 | (private) (private)
2018-11-16 15:00 - 15:30 | (private) (private)
2018-11-16 16:00 - 16:30 | (private) (private)
2018-11-16 16:30 - 17:00 | (private) (private)
2018-11-16 17:00 - 19:00 | (private) (private)
and
$ ruby -Ilib bin/calendar-assistant avail yesterday -r [email protected]
[email protected]
- looking for blocks at least 30 mins long
- between 9am and 6pm in America/Chicago
Availability on Friday, November 16:
โข 9:30am - 10:00am CST (30m)
โข 10:30am - 12:00pm CST (1h 30m)
โข 12:30pm - 1:30pm CST (1h)
โข 3:30pm - 4:00pm CST (30m)
โข 5:00pm - 6:00pm CST (1h)
You can see that there's an event from 17:00-19:00, but the Scheduler is reporting 17:00-18:00 as available.
When my calendar for the day is nearly full,
Linting should create "self + busy" events filling the remaining time,
So that I get some thinking time.
(This needs to be broken down into smaller stories.)
An interesting concept to explore here is: what is "nearly full"? Some ideas:
Also see #2 for related discussion.
In my head I'm imagining some user config like:
defense:
- type: save-chunk
options:
length: 60m
- type: busy-ceiling
options:
max: 360
ignore: "< 25m"
and then running something like:
calendar-assistant lint --defense today
would check my calendar for the day and take action.
Given I have an all-day meeting that's marked as "busy" (transparency: OPAQUE
)
Then the "avail" command should consider me to be unavailable for every day of that all-day event
see #46 for context
15:05:30 firecrest in ~/workspace/calendar-assistant
โ master โ ruby -Ilib bin/calendar-assistant show today
ERROR: could not parse /Users/dwfrank/.calendar-assistant: undefined method `ascii_tree' for nil:NilClass
Is this an authorization fail? Or PEBKAC?
The GCal web UI does this:
Let's do something similar so when I run calendar-assistant show
I see which meetings are likely to get canceled or moved.
This is a likely precursor/blocker for #8 and so it might be nice to add a predicate method or boolean attribute to Event
to reflect this state.
Client credentials are hardcoded to be in a file named credentials.json
in the local directory. Need to put these somewhere else, maybe ~/.calendar-assistant.client_creds.json
or something.
I want to keep these creds separate from the user tokens because, if and when we erect a web UI on top of this, the client creds are global, and the user tokens are user-specific.
We should also make sure that this file is chmodded properly to prevent other users from seeing it (similar to ssh keys, should be 600
or similar).
When I roll off a project, often I need to transfer ownership of events to a remaining team member.
https://developers.google.com/calendar/v3/reference/events/move
As an employee who routinely interacts with clients and others where scheduling is a nightmare, I would love for there to be a way for me to easily (automatically?) share my availability with these external parties.
Given an event has an associated zoom link
When I enter the command calendar-assistant meeting join <profile>
Then the zoom link is opened in the default browser
When I'm on a plane, or on my way to or from the airport,
I should be marked as busy,
Because of physics.
GIven a Tripit or Google calendar event for a flight,
When there's a flight on that calendar,
Then a mirror appointment should be created on my main calendar,
Padded by 90 minutes on each side,
Telling people that I'm busy.
Each option should be able to be set by the environment.
These settings could generally follow the format below
# calendar-assistant --local-store=fixtures.yml
CALENDAR_ASSISTANT_LOCAL_STORE=fixtures.yml calendar-assistant
config preference should be
This should most definitely be broken up into smaller stories, it's intended to capture some high-level thoughts around a command to create and manage one-on-ones (or more generally, people meetings).
Some variations I currently have (manually, with great difficulty) or would like:
Features:
Crazy Implementation Thought
One thought, and this might be crazy, is not to always use GCal's "recurring event" feature (though ice_cube will make that easy), but instead to schedule individual meetings, and then rely on the linter to see "it's been N weeks since your last meeting with Jim, I'll schedule a new one".
Pros: this means that meeting frequency can easily be dialed up or down across a wide variety of one-on-ones. @gsiener has suggested a "dial" for when he's busy and for the next month wants every 2-week one-on-one to become a 3-week; every 3-week to become a 4-week; etc. (Or the other way, let's get some 1:1s done this week!)
Cons: this likely means that one-on-ones won't be on the same day at the same time for people. Regularity is important for many people and this may mean the one-on-one meeting is perceived as less valuable, or is less likely to be attended.
I'd like the Concourse pipeline to report code coverage.
See https://github.com/iandelahorne/codeclimate-resource for an option.
Also see https://docs.codeclimate.com/docs/configuring-test-coverage for the details of how it works.
hanging off #18
consider whether attributes should be on CA or on EventRepository (e.g., calendar) and whether the "default" for CA should change if only one required attendee is present
I'd like the ability to quickly schedule a one-time meeting with someone. Here are some things to consider:
Likely upcoming stories to consider:
A UX idea:
calendar-assistant meet CAL_ID TITLE [OTHER1 [OTHER2 ...]] [-d DURATION]
Question:
Related to #14.
Now that #33 has been completed. It should be trivial to make README generation part of the build.
This will be useful because changes are often made that result in README updates which aren't committed as part of the normal workflow.
At the very least the build could fail if the generated README and committed README do not match
As a user I need to be able to view upcoming events that aren't replied to
so that I can take action on them in my browser
Precursor to #9
Maybe formatted for Gmail? maybe not?
https://github.com/envygeeks/clippy
Just putting this here for reference.
currently it takes 1.6-2.0 seconds for calendar assistant to initialize and run "help".
I think this may have to do with the time of day the specs run? in which case let's invoke timecop
I find myself almost always using one profile, and I think the commands would flow more easily without needing to add the profile. E.g.:
calendar-assistant show work
vs. calendar-assistant show
calendar-assistant location-set work WFH today
vs. calendar-assistant location-set WFH today
I'd suggest calendar-assistant profile-set work, which would then allow subsequent commands to default to that profile. WDYT?
These methods are still sitting in the GCal::Event extensions file event_extensions.rb
. Let's move them into CalendarAssistant::Event:
#human_attendees
#attendee
#response_status
#av_uri
and move the specs as well.
Bonus points to use city-specific emojis
Given I have a location AAA set for three day: today, tomorrow, day after
When I try to set a different location BBB for today
Then the AAA event should be truncated to tomorrow and the day after
What actually happened:
$ date
Wed Nov 14 09:50:57 EST 2018
$ ca location 2018-11-12..friday
[email protected] (all times in America/New_York)
2018-11-12 | ๐บ NYC (not-busy, self)
2018-11-13 | ๐บ NJ (not-busy, self)
2018-11-14 - 2018-11-16 | ๐บ NYC (not-busy, self)
$ ca location-set NJ today
Traceback (most recent call last):
28: from /home/flavorjones/.rvm/gems/ruby-2.5.1/bin/ruby_executable_hooks:24:in `<main>'
27: from /home/flavorjones/.rvm/gems/ruby-2.5.1/bin/ruby_executable_hooks:24:in `eval'
26: from /home/flavorjones/.rvm/gems/ruby-2.5.1/bin/calendar-assistant:23:in `<main>'
25: from /home/flavorjones/.rvm/gems/ruby-2.5.1/bin/calendar-assistant:23:in `load'
24: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/calendar-assistant-0.2.1/bin/calendar-assistant:8:in `<top (required)>'
23: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/thor-0.20.3/lib/thor/base.rb:466:in `start'
22: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/thor-0.20.3/lib/thor.rb:387:in `dispatch'
21: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/thor-0.20.3/lib/thor/invocation.rb:126:in `invoke_command'
20: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/thor-0.20.3/lib/thor/command.rb:27:in `run'
19: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/calendar-assistant-0.2.1/lib/calendar_assistant/cli.rb:160:in `location_set'
18: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/calendar-assistant-0.2.1/lib/calendar_assistant.rb:109:in `create_location_event'
17: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/calendar-assistant-0.2.1/lib/calendar_assistant.rb:109:in `each'
16: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/calendar-assistant-0.2.1/lib/calendar_assistant.rb:114:in `block in create_location_event'
15: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/calendar-assistant-0.2.1/lib/calendar_assistant/event_repository.rb:34:in `update'
14: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/google-api-client-0.24.3/generated/google/apis/calendar_v3/service.rb:1656:in `update_event'
13: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/google-api-client-0.24.3/lib/google/apis/core/base_service.rb:360:in `execute_or_queue_command'
12: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/google-api-client-0.24.3/lib/google/apis/core/http_command.rb:93:in `execute'
11: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/retriable-3.1.2/lib/retriable.rb:56:in `retriable'
10: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/retriable-3.1.2/lib/retriable.rb:56:in `times'
9: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/retriable-3.1.2/lib/retriable.rb:61:in `block in retriable'
8: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/google-api-client-0.24.3/lib/google/apis/core/http_command.rb:101:in `block in execute'
7: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/retriable-3.1.2/lib/retriable.rb:56:in `retriable'
6: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/retriable-3.1.2/lib/retriable.rb:56:in `times'
5: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/retriable-3.1.2/lib/retriable.rb:61:in `block in retriable'
4: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/google-api-client-0.24.3/lib/google/apis/core/http_command.rb:104:in `block (2 levels) in execute'
3: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/google-api-client-0.24.3/lib/google/apis/core/http_command.rb:299:in `execute_once'
2: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/google-api-client-0.24.3/lib/google/apis/core/http_command.rb:183:in `process_response'
1: from /home/flavorjones/.rvm/gems/ruby-2.5.1/gems/google-api-client-0.24.3/lib/google/apis/core/api_command.rb:116:in `check_status'
/home/flavorjones/.rvm/gems/ruby-2.5.1/gems/google-api-client-0.24.3/lib/google/apis/core/http_command.rb:218:in `check_status': required: Missing end time. (Google::Apis::ClientError)
Two style attributes I'd like to make sure are enforced:
Sometimes people may set the meeting "show my time as" to Free instead of busy, or maybe set the visibility to something weird like "Private" which can confuse others as they try and schedule meetings with you.
Linter should probably catch these abnormal thingies, and leave off all-day events that are often marked as "free"
Running with rspec -p1
shows:
Top 1 slowest examples (0.98886 seconds, 24.5% of total time):
CalendarAssistant using the local file store reads from those events
0.98886 seconds ./spec/calendar_assistant_spec.rb:75
The next-slowest spec is 0.1 seconds. Can we look into why this is slow?
As someone who's going on vacation
I want to mark my calendar as completely busy all day
So people know I'm not available
This is probably just a flag on the location set
command, e.g.
calendar-assistant location set work --busy "Sipping Mai Tais" 2019-10-01...2019-10-05
or
calendar-assistant location set work -b "Sipping Mai Tais" 2019-10-01...2019-10-05
Today if I mistype a calendar ID, the result is messy:
$ calendar-assistant avail -a [email protected],[email protected]
Traceback (most recent call last):
39: from /home/flavorjones/.rvm/gems/ruby-2.5.3/bin/ruby_executable_hooks:24:in `<main>'
38: from /home/flavorjones/.rvm/gems/ruby-2.5.3/bin/ruby_executable_hooks:24:in `eval'
37: from /home/flavorjones/.rvm/gems/ruby-2.5.3/bin/calendar-assistant:23:in `<main>'
36: from /home/flavorjones/.rvm/gems/ruby-2.5.3/bin/calendar-assistant:23:in `load'
35: from /home/flavorjones/.rvm/gems/ruby-2.5.3/gems/calendar-assistant-0.5.0/bin/calendar-assistant:8:in `<top (required)>'
... <snip> ...
10: from /home/flavorjones/.rvm/gems/ruby-2.5.3/gems/retriable-3.1.2/lib/retriable.rb:56:in `times'
9: from /home/flavorjones/.rvm/gems/ruby-2.5.3/gems/retriable-3.1.2/lib/retriable.rb:61:in `block in retriable'
8: from /home/flavorjones/.rvm/gems/ruby-2.5.3/gems/google-api-client-0.24.3/lib/google/apis/core/http_command.rb:101:in `block in execute'
7: from /home/flavorjones/.rvm/gems/ruby-2.5.3/gems/retriable-3.1.2/lib/retriable.rb:56:in `retriable'
6: from /home/flavorjones/.rvm/gems/ruby-2.5.3/gems/retriable-3.1.2/lib/retriable.rb:56:in `times'
5: from /home/flavorjones/.rvm/gems/ruby-2.5.3/gems/retriable-3.1.2/lib/retriable.rb:61:in `block in retriable'
4: from /home/flavorjones/.rvm/gems/ruby-2.5.3/gems/google-api-client-0.24.3/lib/google/apis/core/http_command.rb:104:in `block (2 levels) in execute'
3: from /home/flavorjones/.rvm/gems/ruby-2.5.3/gems/google-api-client-0.24.3/lib/google/apis/core/http_command.rb:299:in `execute_once'
2: from /home/flavorjones/.rvm/gems/ruby-2.5.3/gems/google-api-client-0.24.3/lib/google/apis/core/http_command.rb:183:in `process_response'
1: from /home/flavorjones/.rvm/gems/ruby-2.5.3/gems/google-api-client-0.24.3/lib/google/apis/core/api_command.rb:116:in `check_status'
/home/flavorjones/.rvm/gems/ruby-2.5.3/gems/google-api-client-0.24.3/lib/google/apis/core/http_command.rb:218:in `check_status': notFound: Not Found (Google::Apis::ClientError)
Similar to 5ef6514 and 5ae823e and 71a2189, we should make sure we trap this error and emit something human-readable, preferably pointing the user at the incorrect calendar ID.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.