feature-flags: add flag for enabling calendar integration

pull/4316/head jitsi-meet_3789
Saúl Ibarra Corretgé 6 years ago committed by Saúl Ibarra Corretgé
parent 35ffbe1720
commit 97e0303065
  1. 5
      react/features/base/flags/constants.js
  2. 14
      react/features/calendar-sync/actions.web.js
  3. 5
      react/features/calendar-sync/components/CalendarList.native.js
  4. 5
      react/features/calendar-sync/components/CalendarList.web.js
  5. 5
      react/features/calendar-sync/components/CalendarListContent.native.js
  6. 6
      react/features/calendar-sync/components/CalendarListContent.web.js
  7. 5
      react/features/calendar-sync/components/ConferenceNotification.native.js
  8. 13
      react/features/calendar-sync/functions.native.js
  9. 18
      react/features/calendar-sync/functions.web.js
  10. 70
      react/features/calendar-sync/middleware.js
  11. 84
      react/features/calendar-sync/reducer.js
  12. 23
      react/features/conference/components/native/Conference.js
  13. 2
      react/features/settings/components/web/CalendarTab.js
  14. 2
      react/features/settings/components/web/SettingsDialog.js
  15. 8
      react/features/welcome/components/AbstractWelcomePage.js
  16. 4
      react/features/welcome/components/WelcomePage.web.js
  17. 59
      react/features/welcome/components/WelcomePageLists.js

@ -1,9 +1,10 @@
// @flow
/**
* Flag indicating if calendar integration should be disabled.
* Flag indicating if calendar integration should be enabled.
* Default: enabled (true) on Android, auto-detected on iOS.
*/
export const CALENDAR_DISABLED = 'calendar.disabled';
export const CALENDAR_ENABLED = 'calendar.enabled';
/**
* Flag indicating if chat should be enabled.

@ -30,17 +30,19 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
*/
export function bootstrapCalendarIntegration(): Function {
return (dispatch, getState) => {
const state = getState();
if (!isCalendarEnabled(state)) {
return Promise.reject();
}
const {
googleApiApplicationClientID
} = getState()['features/base/config'];
} = state['features/base/config'];
const {
integrationReady,
integrationType
} = getState()['features/calendar-sync'];
if (!isCalendarEnabled()) {
return Promise.reject();
}
} = state['features/calendar-sync'];
return Promise.resolve()
.then(() => {

@ -9,7 +9,6 @@ import { AbstractPage } from '../../base/react';
import { connect } from '../../base/redux';
import { refreshCalendar } from '../actions';
import { isCalendarEnabled } from '../functions';
import styles from './styles';
import CalendarListContent from './CalendarListContent';
@ -138,6 +137,4 @@ function _mapStateToProps(state: Object) {
};
}
export default isCalendarEnabled()
? translate(connect(_mapStateToProps)(CalendarList))
: undefined;
export default translate(connect(_mapStateToProps)(CalendarList));

@ -14,7 +14,6 @@ import {
import { refreshCalendar } from '../actions';
import { ERRORS } from '../constants';
import { isCalendarEnabled } from '../functions';
import CalendarListContent from './CalendarListContent';
@ -257,6 +256,4 @@ function _mapStateToProps(state) {
};
}
export default isCalendarEnabled()
? translate(connect(_mapStateToProps)(CalendarList))
: undefined;
export default translate(connect(_mapStateToProps)(CalendarList));

@ -13,7 +13,6 @@ import { NavigateSectionList } from '../../base/react';
import { connect } from '../../base/redux';
import { refreshCalendar, openUpdateCalendarEventDialog } from '../actions';
import { isCalendarEnabled } from '../functions';
/**
@ -271,6 +270,4 @@ function _mapStateToProps(state: Object) {
};
}
export default isCalendarEnabled()
? translate(connect(_mapStateToProps)(CalendarListContent))
: undefined;
export default translate(connect(_mapStateToProps)(CalendarListContent));

@ -11,8 +11,6 @@ import {
import { MeetingsList } from '../../base/react';
import { connect } from '../../base/redux';
import { isCalendarEnabled } from '../functions';
import AddMeetingUrlButton from './AddMeetingUrlButton';
import JoinButton from './JoinButton';
@ -172,6 +170,4 @@ function _mapStateToProps(state: Object) {
};
}
export default isCalendarEnabled()
? connect(_mapStateToProps)(CalendarListContent)
: undefined;
export default connect(_mapStateToProps)(CalendarListContent);

@ -10,7 +10,6 @@ import { getLocalizedDateFormatter, translate } from '../../base/i18n';
import { connect } from '../../base/redux';
import { ASPECT_RATIO_NARROW } from '../../base/responsive-ui';
import { isCalendarEnabled } from '../functions';
import styles from './styles';
const ALERT_MILLISECONDS = 5 * 60 * 1000;
@ -294,6 +293,4 @@ function _mapStateToProps(state: Object) {
};
}
export default isCalendarEnabled()
? translate(connect(_mapStateToProps)(ConferenceNotification))
: undefined;
export default translate(connect(_mapStateToProps)(ConferenceNotification));

@ -4,6 +4,7 @@ import { NativeModules, Platform } from 'react-native';
import RNCalendarEvents from 'react-native-calendar-events';
import type { Store } from 'redux';
import { CALENDAR_ENABLED, getFeatureFlag } from '../base/flags';
import { getShareInfoText } from '../invite';
import { setCalendarAuthorization } from './actions';
@ -54,12 +55,20 @@ export function addLinkToCalendarEntry(
* Determines whether the calendar feature is enabled by the app. For
* example, Apple through its App Store requires
* {@code NSCalendarsUsageDescription} in the app's Info.plist or App Store
* rejects the app.
* rejects the app. It could also be disabled with a feature flag.
*
* @param {Function|Object} stateful - The redux store or {@code getState}
* function.
* @returns {boolean} If the app has enabled the calendar feature, {@code true};
* otherwise, {@code false}.
*/
export function isCalendarEnabled() {
export function isCalendarEnabled(stateful: Function | Object) {
const flag = getFeatureFlag(stateful, CALENDAR_ENABLED);
if (typeof flag !== 'undefined') {
return flag;
}
const { calendarEnabled = true } = NativeModules.AppInfo;
return calendarEnabled;

@ -16,22 +16,26 @@ import {
import { _updateCalendarEntries } from './functions';
import { googleCalendarApi } from './web/googleCalendar';
import { microsoftCalendarApi } from './web/microsoftCalendar';
import { toState } from '../base/redux';
const logger = require('jitsi-meet-logger').getLogger(__filename);
declare var config: Object;
/**
* Determines whether the calendar feature is enabled by the web.
*
* @param {Function|Object} stateful - The redux store or {@code getState}
* function.
* @returns {boolean} If the app has enabled the calendar feature, {@code true};
* otherwise, {@code false}.
*/
export function isCalendarEnabled() {
return Boolean(
config.enableCalendarIntegration
&& (config.googleApiApplicationClientID
|| config.microsoftApiApplicationClientID));
export function isCalendarEnabled(stateful: Function | Object) {
const {
enableCalendarIntegration,
googleApiApplicationClientID,
microsoftApiApplicationClientID
} = toState(stateful)['features/base/config'] || {};
return Boolean(enableCalendarIntegration && (googleApiApplicationClientID || microsoftApiApplicationClientID));
}
/* eslint-disable no-unused-vars */

@ -9,51 +9,55 @@ import { setCalendarAuthorization } from './actions';
import { REFRESH_CALENDAR } from './actionTypes';
import { _fetchCalendarEntries, isCalendarEnabled } from './functions';
isCalendarEnabled()
&& MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case ADD_KNOWN_DOMAINS: {
// XXX Fetch new calendar entries only when an actual domain has
// become known.
const { getState } = store;
const oldValue = getState()['features/base/known-domains'];
const result = next(action);
const newValue = getState()['features/base/known-domains'];
MiddlewareRegistry.register(store => next => action => {
const { getState } = store;
equals(oldValue, newValue)
|| _fetchCalendarEntries(store, false, false);
if (!isCalendarEnabled(getState)) {
return next(action);
}
return result;
}
switch (action.type) {
case ADD_KNOWN_DOMAINS: {
// XXX Fetch new calendar entries only when an actual domain has
// become known.
const oldValue = getState()['features/base/known-domains'];
const result = next(action);
const newValue = getState()['features/base/known-domains'];
case APP_STATE_CHANGED: {
const result = next(action);
equals(oldValue, newValue)
|| _fetchCalendarEntries(store, false, false);
_maybeClearAccessStatus(store, action);
return result;
}
return result;
}
case APP_STATE_CHANGED: {
const result = next(action);
case SET_CONFIG: {
const result = next(action);
_maybeClearAccessStatus(store, action);
_fetchCalendarEntries(store, false, false);
return result;
}
return result;
}
case SET_CONFIG: {
const result = next(action);
case REFRESH_CALENDAR: {
const result = next(action);
_fetchCalendarEntries(store, false, false);
_fetchCalendarEntries(
store, action.isInteractive, action.forcePermission);
return result;
}
return result;
}
}
case REFRESH_CALENDAR: {
const result = next(action);
return next(action);
});
_fetchCalendarEntries(
store, action.isInteractive, action.forcePermission);
return result;
}
}
return next(action);
});
/**
* Clears the calendar access status when the app comes back from the

@ -44,52 +44,54 @@ const STORE_NAME = 'features/calendar-sync';
* runtime value to see if we need to re-request the calendar permission from
* the user.
*/
isCalendarEnabled()
&& PersistenceRegistry.register(STORE_NAME, {
integrationType: true,
msAuthState: true
});
isCalendarEnabled()
&& ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
switch (action.type) {
case CLEAR_CALENDAR_INTEGRATION:
return DEFAULT_STATE;
case SET_CALENDAR_AUTH_STATE: {
if (!action.msAuthState) {
// received request to delete the state
return set(state, 'msAuthState', undefined);
}
return set(state, 'msAuthState', {
...state.msAuthState,
...action.msAuthState
});
PersistenceRegistry.register(STORE_NAME, {
integrationType: true,
msAuthState: true
});
ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
if (!isCalendarEnabled(state)) {
return state;
}
switch (action.type) {
case CLEAR_CALENDAR_INTEGRATION:
return DEFAULT_STATE;
case SET_CALENDAR_AUTH_STATE: {
if (!action.msAuthState) {
// received request to delete the state
return set(state, 'msAuthState', undefined);
}
case SET_CALENDAR_AUTHORIZATION:
return set(state, 'authorization', action.authorization);
return set(state, 'msAuthState', {
...state.msAuthState,
...action.msAuthState
});
}
case SET_CALENDAR_ERROR:
return set(state, 'error', action.error);
case SET_CALENDAR_AUTHORIZATION:
return set(state, 'authorization', action.authorization);
case SET_CALENDAR_EVENTS:
return set(state, 'events', action.events);
case SET_CALENDAR_ERROR:
return set(state, 'error', action.error);
case SET_CALENDAR_INTEGRATION:
return {
...state,
integrationReady: action.integrationReady,
integrationType: action.integrationType
};
case SET_CALENDAR_EVENTS:
return set(state, 'events', action.events);
case SET_CALENDAR_PROFILE_EMAIL:
return set(state, 'profileEmail', action.email);
case SET_CALENDAR_INTEGRATION:
return {
...state,
integrationReady: action.integrationReady,
integrationType: action.integrationType
};
case SET_LOADING_CALENDAR_EVENTS:
return set(state, 'isLoadingEvents', action.isLoadingEvents);
}
case SET_CALENDAR_PROFILE_EMAIL:
return set(state, 'profileEmail', action.email);
return state;
});
case SET_LOADING_CALENDAR_EVENTS:
return set(state, 'isLoadingEvents', action.isLoadingEvents);
}
return state;
});

@ -13,7 +13,7 @@ import {
makeAspectRatioAware
} from '../../../base/responsive-ui';
import { TestConnectionInfo } from '../../../base/testing';
import { ConferenceNotification } from '../../../calendar-sync';
import { ConferenceNotification, isCalendarEnabled } from '../../../calendar-sync';
import { Chat } from '../../../chat';
import { DisplayNameLabel } from '../../../display-name';
import {
@ -42,6 +42,13 @@ import type { AbstractProps } from '../AbstractConference';
*/
type Props = AbstractProps & {
/**
* Wherther the calendar feature is enabled or not.
*
* @private
*/
_calendarEnabled: boolean,
/**
* The indicator which determines that we are still connecting to the
* conference which includes establishing the XMPP connection and then
@ -331,10 +338,10 @@ class Conference extends AbstractConference<Props, *> {
* @returns {React$Node}
*/
_renderConferenceNotification() {
// XXX If the calendar feature is disabled on a platform, then we don't
// have its components exported so an undefined check is necessary.
const { _calendarEnabled, _reducedUI } = this.props;
return (
!this.props._reducedUI && ConferenceNotification
_calendarEnabled && !_reducedUI
? <ConferenceNotification />
: undefined);
}
@ -417,6 +424,14 @@ function _mapStateToProps(state) {
return {
...abstractMapStateToProps(state),
/**
* Wherther the calendar feature is enabled or not.
*
* @private
* @type {boolean}
*/
_calendarEnabled: isCalendarEnabled(state),
/**
* The indicator which determines that we are still connecting to the
* conference which includes establishing the XMPP connection and then

@ -285,7 +285,7 @@ function _mapStateToProps(state) {
googleApiApplicationClientID,
microsoftApiApplicationClientID
} = state['features/base/config'];
const calendarEnabled = isCalendarEnabled();
const calendarEnabled = isCalendarEnabled(state);
return {
_appName: interfaceConfig.APP_NAME,

@ -135,7 +135,7 @@ function _mapStateToProps(state) {
const showProfileSettings
= configuredTabs.includes('profile') && jwt.isGuest;
const showCalendarSettings
= configuredTabs.includes('calendar') && isCalendarEnabled();
= configuredTabs.includes('calendar') && isCalendarEnabled(state);
const tabs = [];
if (showDeviceSettings) {

@ -6,6 +6,7 @@ import type { Dispatch } from 'redux';
import { createWelcomePageEvent, sendAnalytics } from '../../analytics';
import { appNavigate } from '../../app';
import { isCalendarEnabled } from '../../calendar-sync';
import { isRoomValid } from '../../base/conference';
/**
@ -13,6 +14,11 @@ import { isRoomValid } from '../../base/conference';
*/
type Props = {
/**
* Whether the calendar functionality is enabled or not.
*/
_calendarEnabled: boolean,
/**
* Room name to join to.
*/
@ -237,12 +243,14 @@ export class AbstractWelcomePage extends Component<Props, *> {
* @param {Object} state - The redux state.
* @protected
* @returns {{
* _calendarEnabled: boolean,
* _room: string,
* _settings: Object
* }}
*/
export function _mapStateToProps(state: Object) {
return {
_calendarEnabled: isCalendarEnabled(state),
_room: state['features/base/conference'].room,
_settings: state['features/base/settings']
};

@ -225,11 +225,11 @@ class WelcomePage extends AbstractWelcomePage {
return null;
}
const { t } = this.props;
const { _calendarEnabled, t } = this.props;
const tabs = [];
if (CalendarList) {
if (_calendarEnabled) {
tabs.push({
label: t('welcomepage.calendar'),
content: <CalendarList />

@ -5,7 +5,7 @@ import React, { Component } from 'react';
import { translate } from '../../base/i18n';
import { PagedList } from '../../base/react';
import { connect } from '../../base/redux';
import { CalendarList } from '../../calendar-sync';
import { CalendarList, isCalendarEnabled } from '../../calendar-sync';
import { RecentList } from '../../recent-list';
import { setWelcomePageListsDefaultPage } from '../actions';
@ -15,6 +15,11 @@ import { setWelcomePageListsDefaultPage } from '../actions';
*/
type Props = {
/**
* Whether the calendar functionality is enabled or not.
*/
_calendarEnabled: boolean,
/**
* The stored default page index.
*/
@ -40,19 +45,6 @@ type Props = {
* Implements the lists displayed on the mobile welcome screen.
*/
class WelcomePageLists extends Component<Props> {
/**
* The pages to be rendered.
*
* Note: An element's {@code component} may be {@code undefined} if a
* feature (such as Calendar) is disabled, and that means that the page must
* not be rendered.
*/
pages: Array<{
component: ?Object,
icon: string | number,
title: string
}>;
/**
* Initializes a new {@code WelcomePageLists} instance.
*
@ -61,21 +53,6 @@ class WelcomePageLists extends Component<Props> {
constructor(props) {
super(props);
const { t } = props;
this.pages = [
{
component: RecentList,
icon: 'restore',
title: t('welcomepage.recentList')
},
{
component: CalendarList,
icon: 'event_note',
title: t('welcomepage.calendar')
}
];
// Bind event handlers so they are only bound once per instance.
this._onSelectPage = this._onSelectPage.bind(this);
}
@ -86,18 +63,36 @@ class WelcomePageLists extends Component<Props> {
* @inheritdoc
*/
render() {
const { _defaultPage } = this.props;
const { _calendarEnabled, _defaultPage, t } = this.props;
if (typeof _defaultPage === 'undefined') {
return null;
}
const pages = [
{
component: RecentList,
icon: 'restore',
title: t('welcomepage.recentList')
}
];
if (_calendarEnabled) {
pages.push(
{
component: CalendarList,
icon: 'event_note',
title: t('welcomepage.calendar')
}
);
}
return (
<PagedList
defaultPage = { _defaultPage }
disabled = { this.props.disabled }
onSelectPage = { this._onSelectPage }
pages = { this.pages } />
pages = { pages } />
);
}
@ -122,6 +117,7 @@ class WelcomePageLists extends Component<Props> {
* @param {Object} state - The redux state.
* @protected
* @returns {{
* _calendarEnabled: boolean,
* _defaultPage: number
* }}
*/
@ -135,6 +131,7 @@ function _mapStateToProps(state: Object) {
}
return {
_calendarEnabled: isCalendarEnabled(state),
_defaultPage: defaultPage
};
}

Loading…
Cancel
Save