feature-flags: add flag for enabling calendar integration
This commit is contained in:
committed by
Saúl Ibarra Corretgé
parent
35ffbe1720
commit
97e0303065
@@ -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'];
|
||||
|
||||
equals(oldValue, newValue)
|
||||
|| _fetchCalendarEntries(store, false, false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case APP_STATE_CHANGED: {
|
||||
const result = next(action);
|
||||
|
||||
_maybeClearAccessStatus(store, action);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case SET_CONFIG: {
|
||||
const result = next(action);
|
||||
|
||||
_fetchCalendarEntries(store, false, false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case REFRESH_CALENDAR: {
|
||||
const result = next(action);
|
||||
|
||||
_fetchCalendarEntries(
|
||||
store, action.isInteractive, action.forcePermission);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const { getState } = store;
|
||||
|
||||
if (!isCalendarEnabled(getState)) {
|
||||
return next(action);
|
||||
});
|
||||
}
|
||||
|
||||
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'];
|
||||
|
||||
equals(oldValue, newValue)
|
||||
|| _fetchCalendarEntries(store, false, false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case APP_STATE_CHANGED: {
|
||||
const result = next(action);
|
||||
|
||||
_maybeClearAccessStatus(store, action);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case SET_CONFIG: {
|
||||
const result = next(action);
|
||||
|
||||
_fetchCalendarEntries(store, false, false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case REFRESH_CALENDAR: {
|
||||
const result = 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
|
||||
});
|
||||
}
|
||||
|
||||
case SET_CALENDAR_AUTHORIZATION:
|
||||
return set(state, 'authorization', action.authorization);
|
||||
|
||||
case SET_CALENDAR_ERROR:
|
||||
return set(state, 'error', action.error);
|
||||
|
||||
case SET_CALENDAR_EVENTS:
|
||||
return set(state, 'events', action.events);
|
||||
|
||||
case SET_CALENDAR_INTEGRATION:
|
||||
return {
|
||||
...state,
|
||||
integrationReady: action.integrationReady,
|
||||
integrationType: action.integrationType
|
||||
};
|
||||
|
||||
case SET_CALENDAR_PROFILE_EMAIL:
|
||||
return set(state, 'profileEmail', action.email);
|
||||
|
||||
case SET_LOADING_CALENDAR_EVENTS:
|
||||
return set(state, 'isLoadingEvents', action.isLoadingEvents);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
return set(state, 'msAuthState', {
|
||||
...state.msAuthState,
|
||||
...action.msAuthState
|
||||
});
|
||||
}
|
||||
|
||||
case SET_CALENDAR_AUTHORIZATION:
|
||||
return set(state, 'authorization', action.authorization);
|
||||
|
||||
case SET_CALENDAR_ERROR:
|
||||
return set(state, 'error', action.error);
|
||||
|
||||
case SET_CALENDAR_EVENTS:
|
||||
return set(state, 'events', action.events);
|
||||
|
||||
case SET_CALENDAR_INTEGRATION:
|
||||
return {
|
||||
...state,
|
||||
integrationReady: action.integrationReady,
|
||||
integrationType: action.integrationType
|
||||
};
|
||||
|
||||
case SET_CALENDAR_PROFILE_EMAIL:
|
||||
return set(state, 'profileEmail', action.email);
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user