-
Notifications
You must be signed in to change notification settings - Fork 6.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Google & Microsoft calendar API integration #3340
Conversation
For the web part it just adds new property to enable/disable calendar web integration, disabled by default.
package.json
Outdated
"jwt-decode": "2.2.0", | ||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#2be752fc88ff71e454c6b9178b21a33b59c53f41", | ||
"lodash": "4.17.4", | ||
"@microsoft/microsoft-graph-client": "1.1.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you manually add this dependency? I think if you added it using npm's --save
it would have been added with the other packages that start with @.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, manually. I see. I will move it.
}); | ||
|
||
if (!api) { | ||
return Promise.resolve(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be Promise.reject instead of resolving?
} | ||
|
||
return calendarIntegrationInstance; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unsure how I feel about the calendarIntegration Instance being stored in this file. I think it's fine but I also feel it might be something that could be stored in the redux state instead or held onto by some singleton.
@@ -79,6 +80,11 @@ StateListenerRegistry.register( | |||
/* listener */ (googleAPIState, { dispatch }) => { | |||
dispatch(setCalendarAPIState(googleAPIState)); | |||
}); | |||
StateListenerRegistry.register( | |||
/* selector */ state => state['features/google-api'].profileEmail, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels off to me that there is now duplicated state. I think you mentioned you weren't sure about the whole profileEmail thing. Why does the same state need to be in calendar and google api? Could the components needing the email look in multiple places for it instead, maybe through a selector/helper that knows where to look in redux?
Or maybe they're separate enough concerns to keep the two separate state entries?
init(dispatch: Function, getState: Function): Promise<void> { | ||
if (getState()['features/calendar-sync'].apiState !== 0) { | ||
init(): Promise<void> { | ||
if (this._getState()['features/calendar-sync'].apiState !== 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btw, the 0 should probably be the constant for the google api state.
|
||
// not authorized, skip | ||
if (!getState()['features/calendar-sync'].msAuthState) { | ||
return Promise.resolve(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would a reject be better here?
} | ||
|
||
/** | ||
* Prompts the participant to sign in.. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double period. I'm blocking this PR no matter what now. I dream of a world where there are no more double periods.
*/ | ||
export function signIn() { | ||
return (dispatch: Dispatch<*>, getState: Function): Promise<*> => { | ||
const calendarType = getState()['features/calendar-sync'].calendarType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: there's a preference to declare variables with destructuring where preferable. So const { calendarType } = getState()['features/calendar-sync']
. I guess there's no linter enforcement for that.
/** | ||
* The max number of events to fetch from the calendar. | ||
*/ | ||
export const MAX_LIST_LENGTH = 10; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe grouping these constants together in an object would be consistent with the other constants? Also, what does ther -1
mean for FETCH_START_DAYS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, these were always separated. I'd prefer not changing existing code as much as possible for now. Any comment requesting change to existing logic you should just say no.
} | ||
|
||
return null; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All these helpers were pre-existing but have been moved around with no changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, there was only one change I made. 7f8a1e2#diff-e626ccf6b0437daab4a769da852e453dR24
It was executing one unnecessary fetch every time because of comparing arrays, at least that what I saw on mobile, not sure whether that array === array is working differently on mobile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. So instead of comparing array values it was comparing if they're pointers to the same array.
* otherwise, {@code false}. | ||
*/ | ||
export function _isCalendarEnabled() { | ||
return config.enableCalendarIntegration === true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing to look out for, if possible, is it is preferred to get config from redux.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was not sure how to do that in the constants, as this was the way it was implemented for mobile, so I didn't want to change that logic.
/* eslint-enable max-params */ | ||
|
||
if (this._isSignedIn()) { | ||
this._dispatch(setCalendarAPIState(CALENDAR_API_STATES.SIGNED_IN)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does a getCalendarEntries function dispatch being signed in?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have token that is not expired and you reload the page, this will set the correct state of the calendar-sync that it is signed in, have a correct token ... Maybe this state should be done in one place when we init the api or when we construct the MicrosoftCalendarApi object ..., WDYT?
The fact is that currently when you load the welcome page it calls _fetchCalendarEntries which end up calling getCalendarEntries and I was using that to set the correct state of the feature ...
tokenExpires: expires.getTime(), | ||
idToken | ||
})); | ||
this._dispatch(setCalendarProfileEmail(payload.preferred_username)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function could be more concise by not doing any dispatching and instead having the thing that uses the result of the function doing the dispatching. Right now this token not only gets valid token parts but also updates calendar auth state and username, when I think it doesn't have to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I will change it, thanks. The caller already dispatches signed_id state ... so it should also dispatches and the rest ....
@@ -0,0 +1,492 @@ | |||
/* @flow */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are token expiration and renewal handled yet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, this is not handled yet.
* | ||
* @type {Array<string>} | ||
*/ | ||
export const GOOGLE_API_SCOPES = [ | ||
'https://www.googleapis.com/auth/youtube.readonly' | ||
'https://www.googleapis.com/auth/youtube.readonly', | ||
'https://www.googleapis.com/auth/calendar' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there any changes needed to deployed applications to get calendar permissions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so, you need to just enable that API. We should take care of that about meet.jit.si. I was also thinking of dropping a simple README with links to Google and Microsoft dashboard how to enable these.
endDate.setDate(endDate.getDate() + fetchEndDays); | ||
|
||
return this._getGoogleApiClient() | ||
.client.calendar.events.list({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you look at the google api request batching? I'm wondering if it's super easy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean to get rid of some of the Promises part?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, to make it so there aren't multiple requests going out at once and instead are batched into one request. Now I think we should punt on it considering the breadth of this PR.
|
||
const query = client.api('/me/calendars'); | ||
|
||
query.get((error, response) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I read this in the microsoft-graph-client readme: The actions(.get(), .put(), etc.) accept a callback or don't pass in a function to get back a Promise.
That might mean you don't need the wrapper.
@@ -0,0 +1,11 @@ | |||
<html> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if it's best to include the doctype. According to W3C it is required but I don't think omitting it actually causes much harm for this case.
The case where there is a calendar type google selected, but not logged in, trying to login on loading welcome page will show a warning that it tried to open a popup, which was denied by browser.
}); | ||
|
||
if (!api) { | ||
return Promise.reject('No calendar type selected!'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to do this check even before trying to get the api?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, I get what you're doing. In case some unexpected type is set you want to error here.
this._currentRequest = {}; | ||
|
||
// checks post messages for events from the popup | ||
window.addEventListener('message', this._onPostMessage.bind(this)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If switching between microsoft and google, would this listener cause any problems? Do you need to do any cleanup in a deconstructor/destroy method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see how it can cause any problems, but cleaning is something we should take care. I will check it now.
@@ -256,6 +256,10 @@ var config = { | |||
// maintenance at 01:00 AM GMT, | |||
// noticeMessage: '', | |||
|
|||
// Enables calendar integration, depends on googleApiApplicationClientID | |||
// and microsoftApiApplicationClientID | |||
// enableCalendarIntegration: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe microsoftApiApplicationClientID needs to be added as a separate entry in config. googleApiApplicationClientID is listed under undocumented config values and is also present in the overrides whitelist.
* type: CLEAR_CALENDAR_INTEGRATION | ||
* } | ||
*/ | ||
export const CLEAR_CALENDAR_INTEGRATION = Symbol('CLEAR_CALENDAR_INTEGRATION'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to rename these to something? I had used the word "integration" to avoid conflicting with existing names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We agreed to leave it as integration.
* calendarType: ?string | ||
* } | ||
*/ | ||
export const SET_CALENDAR_TYPE = Symbol('SET_CALENDAR_TYPE'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think SET_CALENDAR_TYPE can be removed. Probably have to do a pass of all actions/actionTypes to make sure any unused ones are cleaned up.
integrationType | ||
} = getState()['features/calendar-sync']; | ||
|
||
if (!enableCalendarIntegration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this should use the function for checking if calendar integration is enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also second-guessing the usefulness of this for the welcome page. Will the welcome page be calling this?
return Promise.reject('No integration found'); | ||
} | ||
|
||
// FIXME BEFORE MERGE: Where is the actual setting happening? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One issue here is the google-api doesn't call setCalendarProfileEmail but microsoftCalendar does. There should be consistency on where setCalendarProfileEmail is being called.
}); | ||
|
||
return Promise.all(promises) | ||
.then(result => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think naming this "results" might make it more obvious, if it isn't already, that there are multiple results in an array that need to be flattened.
/** | ||
* The current calendar integration in use, if any. | ||
*/ | ||
_isConnectedToCalendar: ?Object, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a boolean now.
*/ | ||
componentDidMount() { | ||
this.props.dispatch(bootstrapCalendarIntegration()) | ||
.catch(() => console.warn('this error right here...')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotta remove this debugging code.
* @param {Object} state - The Redux state. | ||
* @private | ||
* @returns {{ | ||
* _enableGoogleIntegration: boolean, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have to update this jsdoc for the proper return.
import { | ||
getMoreTabProps, | ||
getProfileTabProps | ||
} from '../../functions'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Undo this change.
List microsoftApiApplicationClientID in undocument config. remove unused SET_CALENDAR_TYPE action use helper for calendar enabled in bootstrap reorder actions reorder imports change order of signin -> set type -> update profile add logging for signout error reword setting dialog desc to avoid redundancy add jsdoc to microsoft button props reorder calendar constants move default state to reducer (not reused anywhere) update comment about calendar-sync due to removal of getCalendarState update comment for getCalendarIntegration remove vague comment alpha order reducer, return default state on reset alpha order persistence registry remove unnecessary getType from apis update comments in microsoftCalendar alpha order google-api exports, use api.get in loadGoogleAPI set jsdoc for google signin props alpha order googleapi methods fix calendartab docs
* @returns {Object} | ||
*/ | ||
function getParamsFromHash(hash) { | ||
const params = new URLSearchParams(hash.slice(1, hash.length)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like "parseURLParams" exists somewhere and we should use that.
The web part needs configuration in order to refresh tokens (Microsoft).
updateProfile changed to getCurrentEmail rename result to results stop storing integration in redux, store if ready for use use existing helpers to parse redirect url
No description provided.