Skip to content
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

Merged
merged 24 commits into from
Aug 15, 2018
Merged

Conversation

damencho
Copy link
Member

@damencho damencho commented Aug 2, 2018

No description provided.

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",
Copy link
Contributor

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 @.

Copy link
Member Author

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();
Copy link
Contributor

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;
Copy link
Contributor

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,
Copy link
Contributor

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) {
Copy link
Contributor

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();
Copy link
Contributor

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..
Copy link
Contributor

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;
Copy link
Contributor

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;
Copy link
Contributor

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?

Copy link
Contributor

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;
}
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

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;
Copy link
Contributor

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.

Copy link
Member Author

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));
Copy link
Contributor

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?

Copy link
Member Author

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));
Copy link
Contributor

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.

Copy link
Member Author

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 */
Copy link
Contributor

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?

Copy link
Member Author

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'
Copy link
Contributor

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?

Copy link
Member Author

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({
Copy link
Contributor

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.

Copy link
Member Author

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?

Copy link
Contributor

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) => {
Copy link
Contributor

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>
Copy link
Contributor

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.

});

if (!api) {
return Promise.reject('No calendar type selected!');
Copy link
Contributor

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?

Copy link
Contributor

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));
Copy link
Contributor

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?

Copy link
Member Author

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,
Copy link
Contributor

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');
Copy link
Contributor

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.

Copy link
Contributor

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');
Copy link
Contributor

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) {
Copy link
Contributor

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.

Copy link
Contributor

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?
Copy link
Contributor

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 =>
Copy link
Contributor

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,
Copy link
Contributor

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...'))
Copy link
Contributor

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,
Copy link
Contributor

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';
Copy link
Contributor

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));
Copy link
Contributor

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.

damencho and others added 4 commits August 14, 2018 16:12
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
virtuacoplenny
virtuacoplenny previously approved these changes Aug 15, 2018
@virtuacoplenny virtuacoplenny merged commit 7eda313 into master Aug 15, 2018
@damencho damencho deleted the calendar-web branch August 17, 2018 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants