chore: Parallelise feature flags and user info API (#12263)

This commit is contained in:
arunvjn 2022-04-07 23:27:32 +05:30 committed by GitHub
parent 461f35380b
commit fabfb65a4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 153 additions and 113 deletions

View File

@ -48,14 +48,15 @@ import { trimTrailingSlash } from "utils/helpers";
import { getSafeCrash, getSafeCrashCode } from "selectors/errorSelectors"; import { getSafeCrash, getSafeCrashCode } from "selectors/errorSelectors";
import UserProfile from "pages/UserProfile"; import UserProfile from "pages/UserProfile";
import { getCurrentUser } from "actions/authActions"; import { getCurrentUser } from "actions/authActions";
import { getFeatureFlagsFetched } from "selectors/usersSelectors"; import { selectFeatureFlags } from "selectors/usersSelectors";
import Setup from "pages/setup"; import Setup from "pages/setup";
import Settings from "pages/Settings"; import Settings from "pages/Settings";
import SignupSuccess from "pages/setup/SignupSuccess"; import SignupSuccess from "pages/setup/SignupSuccess";
import { Theme } from "constants/DefaultTheme"; import { Theme } from "constants/DefaultTheme";
import { ERROR_CODES } from "ce/constants/ApiConstants"; import { ERROR_CODES } from "ce/constants/ApiConstants";
import TemplatesListLoader from "pages/Templates/loader"; import TemplatesListLoader from "pages/Templates/loader";
import getFeatureFlags from "utils/featureFlags"; import { fetchFeatureFlagsInit } from "actions/userActions";
import FeatureFlags from "entities/FeatureFlags";
const SentryRoute = Sentry.withSentryRouting(Route); const SentryRoute = Sentry.withSentryRouting(Route);
@ -78,19 +79,21 @@ function changeAppBackground(currentTheme: any) {
function AppRouter(props: { function AppRouter(props: {
safeCrash: boolean; safeCrash: boolean;
getCurrentUser: () => void; getCurrentUser: () => void;
getFeatureFlags: () => void;
currentTheme: Theme; currentTheme: Theme;
featureFlagsFetched: boolean;
safeCrashCode?: ERROR_CODES; safeCrashCode?: ERROR_CODES;
featureFlags: FeatureFlags;
setTheme: (theme: ThemeMode) => void; setTheme: (theme: ThemeMode) => void;
}) { }) {
const { featureFlags, getCurrentUser, getFeatureFlags } = props;
useEffect(() => { useEffect(() => {
AnalyticsUtil.logEvent("ROUTE_CHANGE", { path: window.location.pathname }); AnalyticsUtil.logEvent("ROUTE_CHANGE", { path: window.location.pathname });
const stopListener = history.listen((location: any) => { const stopListener = history.listen((location: any) => {
AnalyticsUtil.logEvent("ROUTE_CHANGE", { path: location.pathname }); AnalyticsUtil.logEvent("ROUTE_CHANGE", { path: location.pathname });
changeAppBackground(props.currentTheme); changeAppBackground(props.currentTheme);
}); });
props.getCurrentUser(); getCurrentUser();
getFeatureFlags();
return stopListener; return stopListener;
}, []); }, []);
@ -98,8 +101,6 @@ function AppRouter(props: {
changeAppBackground(props.currentTheme); changeAppBackground(props.currentTheme);
}, [props.currentTheme]); }, [props.currentTheme]);
if (!props.featureFlagsFetched) return null;
return ( return (
<Router history={history}> <Router history={history}>
<Suspense fallback={loadingIndicator}> <Suspense fallback={loadingIndicator}>
@ -128,14 +129,13 @@ function AppRouter(props: {
exact exact
path={SIGNUP_SUCCESS_URL} path={SIGNUP_SUCCESS_URL}
/> />
<SentryRoute component={UserProfile} path={PROFILE} /> <SentryRoute component={UserProfile} path={PROFILE} />
<SentryRoute <SentryRoute
component={UnsubscribeEmail} component={UnsubscribeEmail}
path={UNSUBSCRIBE_EMAIL_URL} path={UNSUBSCRIBE_EMAIL_URL}
/> />
<SentryRoute component={Setup} exact path={SETUP} /> <SentryRoute component={Setup} exact path={SETUP} />
{getFeatureFlags().APP_TEMPLATE && ( {featureFlags.APP_TEMPLATE && (
<SentryRoute <SentryRoute
component={TemplatesListLoader} component={TemplatesListLoader}
path={TEMPLATES_PATH} path={TEMPLATES_PATH}
@ -174,7 +174,7 @@ const mapStateToProps = (state: AppState) => ({
currentTheme: getCurrentThemeDetails(state), currentTheme: getCurrentThemeDetails(state),
safeCrash: getSafeCrash(state), safeCrash: getSafeCrash(state),
safeCrashCode: getSafeCrashCode(state), safeCrashCode: getSafeCrashCode(state),
featureFlagsFetched: getFeatureFlagsFetched(state), featureFlags: selectFeatureFlags(state),
}); });
const mapDispatchToProps = (dispatch: any) => ({ const mapDispatchToProps = (dispatch: any) => ({
@ -182,6 +182,7 @@ const mapDispatchToProps = (dispatch: any) => ({
dispatch(setThemeMode(mode)); dispatch(setThemeMode(mode));
}, },
getCurrentUser: () => dispatch(getCurrentUser()), getCurrentUser: () => dispatch(getCurrentUser()),
getFeatureFlags: () => dispatch(fetchFeatureFlagsInit()),
}); });
export default connect(mapStateToProps, mapDispatchToProps)(AppRouter); export default connect(mapStateToProps, mapDispatchToProps)(AppRouter);

View File

@ -11,6 +11,7 @@ import {
UpdateUserRequest, UpdateUserRequest,
VerifyTokenRequest, VerifyTokenRequest,
} from "api/UserApi"; } from "api/UserApi";
import FeatureFlags from "entities/FeatureFlags";
export const logoutUser = (payload?: { redirectURL: string }) => ({ export const logoutUser = (payload?: { redirectURL: string }) => ({
type: ReduxActionTypes.LOGOUT_USER_INIT, type: ReduxActionTypes.LOGOUT_USER_INIT,
@ -108,8 +109,9 @@ export const fetchFeatureFlagsInit = () => ({
type: ReduxActionTypes.FETCH_FEATURE_FLAGS_INIT, type: ReduxActionTypes.FETCH_FEATURE_FLAGS_INIT,
}); });
export const fetchFeatureFlagsSuccess = () => ({ export const fetchFeatureFlagsSuccess = (payload: FeatureFlags) => ({
type: ReduxActionTypes.FETCH_FEATURE_FLAGS_SUCCESS, type: ReduxActionTypes.FETCH_FEATURE_FLAGS_SUCCESS,
payload,
}); });
export const fetchFeatureFlagsError = (error: any) => ({ export const fetchFeatureFlagsError = (error: any) => ({

View File

@ -4,13 +4,13 @@ import { Icon, Popover, PopoverPosition } from "@blueprintjs/core";
import { Theme } from "constants/DefaultTheme"; import { Theme } from "constants/DefaultTheme";
import { useSelector, useDispatch } from "react-redux"; import { useSelector, useDispatch } from "react-redux";
import { getIsGitConnected } from "../../../../selectors/gitSyncSelectors"; import { getIsGitConnected } from "../../../../selectors/gitSyncSelectors";
import getFeatureFlags from "utils/featureFlags";
import { setIsGitSyncModalOpen } from "actions/gitSyncActions"; import { setIsGitSyncModalOpen } from "actions/gitSyncActions";
import { GitSyncModalTab } from "entities/GitSync"; import { GitSyncModalTab } from "entities/GitSync";
import { Colors } from "constants/Colors"; import { Colors } from "constants/Colors";
import { ReactComponent as GitBranch } from "assets/icons/ads/git-branch.svg"; import { ReactComponent as GitBranch } from "assets/icons/ads/git-branch.svg";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
import { selectFeatureFlags } from "selectors/usersSelectors";
const DeployLinkDialog = styled.div` const DeployLinkDialog = styled.div`
flex-direction: column; flex-direction: column;
@ -74,6 +74,8 @@ export const DeployLinkButton = withTheme((props: Props) => {
const isGitConnected = useSelector(getIsGitConnected); const isGitConnected = useSelector(getIsGitConnected);
const featureFlags = useSelector(selectFeatureFlags);
const onClose = () => { const onClose = () => {
setIsOpen(false); setIsOpen(false);
}; };
@ -96,7 +98,7 @@ export const DeployLinkButton = withTheme((props: Props) => {
canEscapeKeyClose={false} canEscapeKeyClose={false}
content={ content={
<DeployLinkDialog> <DeployLinkDialog>
{getFeatureFlags().GIT && !isGitConnected && ( {featureFlags.GIT && !isGitConnected && (
<DeployLink <DeployLink
className="t--connect-to-git-btn" className="t--connect-to-git-btn"
onClick={goToGitConnectionPopup} onClick={goToGitConnectionPopup}

View File

@ -36,7 +36,6 @@ import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
import { getEntityNameAndPropertyPath } from "workers/evaluationUtils"; import { getEntityNameAndPropertyPath } from "workers/evaluationUtils";
import { JSCollectionData } from "reducers/entityReducers/jsActionsReducer"; import { JSCollectionData } from "reducers/entityReducers/jsActionsReducer";
import { createNewJSCollection } from "actions/jsPaneActions"; import { createNewJSCollection } from "actions/jsPaneActions";
import getFeatureFlags from "utils/featureFlags";
import { JSAction, Variable } from "entities/JSCollection"; import { JSAction, Variable } from "entities/JSCollection";
import { import {
CLEAR_INTERVAL, CLEAR_INTERVAL,
@ -60,10 +59,11 @@ import {
import { toggleShowGlobalSearchModal } from "actions/globalSearchActions"; import { toggleShowGlobalSearchModal } from "actions/globalSearchActions";
import { filterCategories, SEARCH_CATEGORY_ID } from "../GlobalSearch/utils"; import { filterCategories, SEARCH_CATEGORY_ID } from "../GlobalSearch/utils";
import { ActionDataState } from "reducers/entityReducers/actionsReducer"; import { ActionDataState } from "reducers/entityReducers/actionsReducer";
import { selectFeatureFlags } from "selectors/usersSelectors";
import FeatureFlags from "entities/FeatureFlags";
/* eslint-disable @typescript-eslint/ban-types */ /* eslint-disable @typescript-eslint/ban-types */
/* TODO: Function and object types need to be updated to enable the lint rule */ /* TODO: Function and object types need to be updated to enable the lint rule */
const isJSEditorEnabled = getFeatureFlags().JS_EDITOR;
const baseOptions: { label: string; value: string }[] = [ const baseOptions: { label: string; value: string }[] = [
{ {
label: createMessage(NO_ACTION), label: createMessage(NO_ACTION),
@ -127,7 +127,8 @@ const baseOptions: { label: string; value: string }[] = [
}, },
]; ];
const getBaseOptions = () => { const getBaseOptions = (featureFlags: FeatureFlags) => {
const { JS_EDITOR: isJSEditorEnabled } = featureFlags;
if (isJSEditorEnabled) { if (isJSEditorEnabled) {
const jsOption = baseOptions.find( const jsOption = baseOptions.find(
(option: any) => option.value === ActionType.jsFunction, (option: any) => option.value === ActionType.jsFunction,
@ -413,8 +414,9 @@ function getIntegrationOptionsWithChildren(
jsActions: Array<JSCollectionData>, jsActions: Array<JSCollectionData>,
createIntegrationOption: TreeDropdownOption, createIntegrationOption: TreeDropdownOption,
dispatch: any, dispatch: any,
featureFlags: FeatureFlags,
) { ) {
const isJSEditorEnabled = getFeatureFlags().JS_EDITOR; const { JS_EDITOR: isJSEditorEnabled } = featureFlags;
const createJSObject: TreeDropdownOption = { const createJSObject: TreeDropdownOption = {
label: "New JS Object", label: "New JS Object",
value: "JSObject", value: "JSObject",
@ -527,6 +529,7 @@ function getIntegrationOptionsWithChildren(
function useIntegrationsOptionTree() { function useIntegrationsOptionTree() {
const pageId = useSelector(getCurrentPageId) || ""; const pageId = useSelector(getCurrentPageId) || "";
const applicationId = useSelector(getCurrentApplicationId) as string; const applicationId = useSelector(getCurrentApplicationId) as string;
const featureFlags = useSelector(selectFeatureFlags);
const dispatch = useDispatch(); const dispatch = useDispatch();
const plugins = useSelector((state: AppState) => { const plugins = useSelector((state: AppState) => {
return state.entities.plugins.list; return state.entities.plugins.list;
@ -539,7 +542,7 @@ function useIntegrationsOptionTree() {
pageId, pageId,
applicationId, applicationId,
pluginGroups, pluginGroups,
getBaseOptions(), getBaseOptions(featureFlags),
actions, actions,
jsActions, jsActions,
{ {
@ -557,6 +560,7 @@ function useIntegrationsOptionTree() {
}, },
}, },
dispatch, dispatch,
featureFlags,
); );
} }

View File

@ -1,10 +0,0 @@
type FeatureFlag = {
APP_TEMPLATE: boolean;
JS_EDITOR: boolean;
MULTIPLAYER: boolean;
SNIPPET: boolean;
GIT: boolean;
GIT_IMPORT: boolean;
};
export default FeatureFlag;

View File

@ -0,0 +1,10 @@
type FeatureFlags = {
APP_TEMPLATE?: boolean;
JS_EDITOR?: boolean;
MULTIPLAYER?: boolean;
SNIPPET?: boolean;
GIT?: boolean;
GIT_IMPORT?: boolean;
};
export default FeatureFlags;

View File

@ -27,7 +27,7 @@ import { getIsImportingApplication } from "selectors/applicationSelectors";
import { ReduxActionTypes } from "constants/ReduxActionConstants"; import { ReduxActionTypes } from "constants/ReduxActionConstants";
import Dialog from "../../components/ads/DialogComponent"; import Dialog from "../../components/ads/DialogComponent";
import { Classes } from "@blueprintjs/core"; import { Classes } from "@blueprintjs/core";
import getFeatureFlags from "../../utils/featureFlags"; import { selectFeatureFlags } from "selectors/usersSelectors";
const StyledDialog = styled(Dialog)` const StyledDialog = styled(Dialog)`
&& .${Classes.DIALOG_HEADER} { && .${Classes.DIALOG_HEADER} {
@ -259,7 +259,9 @@ function ImportApplicationModal(props: ImportApplicationModalProps) {
}, [appFileToBeUploaded, importingApplication]); }, [appFileToBeUploaded, importingApplication]);
const onRemoveFile = useCallback(() => setAppFileToBeUploaded(null), []); const onRemoveFile = useCallback(() => setAppFileToBeUploaded(null), []);
const isGitImportFeatureEnabled = getFeatureFlags().GIT_IMPORT;
const featureFlags = useSelector(selectFeatureFlags);
const { GIT_IMPORT: isGitImportFeatureEnabled } = featureFlags;
return ( return (
<StyledDialog <StyledDialog

View File

@ -40,7 +40,7 @@ import { isPermitted, PERMISSION_TYPE } from "./permissionHelpers";
import FormDialogComponent from "components/editorComponents/form/FormDialogComponent"; import FormDialogComponent from "components/editorComponents/form/FormDialogComponent";
import Dialog from "components/ads/DialogComponent"; import Dialog from "components/ads/DialogComponent";
import { User } from "constants/userConstants"; import { User } from "constants/userConstants";
import { getCurrentUser } from "selectors/usersSelectors"; import { getCurrentUser, selectFeatureFlags } from "selectors/usersSelectors";
import { CREATE_ORGANIZATION_FORM_NAME } from "constants/forms"; import { CREATE_ORGANIZATION_FORM_NAME } from "constants/forms";
import { import {
DropdownOnSelectActions, DropdownOnSelectActions,
@ -85,7 +85,6 @@ import {
import { ReactComponent as NoAppsFoundIcon } from "assets/svg/no-apps-icon.svg"; import { ReactComponent as NoAppsFoundIcon } from "assets/svg/no-apps-icon.svg";
import { setHeaderMeta } from "actions/themeActions"; import { setHeaderMeta } from "actions/themeActions";
import getFeatureFlags from "utils/featureFlags";
import SharedUserList from "pages/common/SharedUserList"; import SharedUserList from "pages/common/SharedUserList";
import { useIsMobileDevice } from "utils/hooks/useDeviceDetect"; import { useIsMobileDevice } from "utils/hooks/useDeviceDetect";
import { Indices } from "constants/Layers"; import { Indices } from "constants/Layers";
@ -523,6 +522,7 @@ function ApplicationsSection(props: any) {
) => { ) => {
dispatch(updateApplication(id, data)); dispatch(updateApplication(id, data));
}; };
const featureFlags = useSelector(selectFeatureFlags);
useEffect(() => { useEffect(() => {
// Clears URL params cache // Clears URL params cache
@ -931,7 +931,7 @@ function ApplicationsSection(props: any) {
isMobile={isMobile} isMobile={isMobile}
> >
{organizationsListComponent} {organizationsListComponent}
{getFeatureFlags().GIT_IMPORT && <GitSyncModal isImport />} {featureFlags.GIT_IMPORT && <GitSyncModal isImport />}
<ReconnectDatasourceModal /> <ReconnectDatasourceModal />
</ApplicationContainer> </ApplicationContainer>
); );

View File

@ -23,7 +23,6 @@ import {
} from "../../Applications/permissionHelpers"; } from "../../Applications/permissionHelpers";
import { getCurrentApplication } from "selectors/applicationSelectors"; import { getCurrentApplication } from "selectors/applicationSelectors";
import { Colors } from "constants/Colors"; import { Colors } from "constants/Colors";
import getFeatureFlags from "utils/featureFlags";
import { setIsGitSyncModalOpen } from "actions/gitSyncActions"; import { setIsGitSyncModalOpen } from "actions/gitSyncActions";
import { GitSyncModalTab } from "entities/GitSync"; import { GitSyncModalTab } from "entities/GitSync";
import { getIsGitConnected } from "selectors/gitSyncSelectors"; import { getIsGitConnected } from "selectors/gitSyncSelectors";
@ -38,6 +37,7 @@ import { redoAction, undoAction } from "actions/pageActions";
import { redoShortCut, undoShortCut } from "utils/helpers"; import { redoShortCut, undoShortCut } from "utils/helpers";
import { pageListEditorURL } from "RouteBuilder"; import { pageListEditorURL } from "RouteBuilder";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
import { selectFeatureFlags } from "selectors/usersSelectors";
type NavigationMenuDataProps = ThemeProp & { type NavigationMenuDataProps = ThemeProp & {
editMode: typeof noop; editMode: typeof noop;
@ -122,7 +122,9 @@ export const GetNavigationMenuData = ({
}, },
]; ];
if (getFeatureFlags().GIT && !isGitConnected) { const featureFlags = useSelector(selectFeatureFlags);
if (featureFlags.GIT && !isGitConnected) {
deployOptions.push({ deployOptions.push({
text: createMessage(CONNECT_TO_GIT_OPTION), text: createMessage(CONNECT_TO_GIT_OPTION),
onClick: () => openGitConnectionPopup(), onClick: () => openGitConnectionPopup(),

View File

@ -33,7 +33,7 @@ import {
} from "selectors/applicationSelectors"; } from "selectors/applicationSelectors";
import EditorAppName from "./EditorAppName"; import EditorAppName from "./EditorAppName";
import ProfileDropdown from "pages/common/ProfileDropdown"; import ProfileDropdown from "pages/common/ProfileDropdown";
import { getCurrentUser } from "selectors/usersSelectors"; import { getCurrentUser, selectFeatureFlags } from "selectors/usersSelectors";
import { ANONYMOUS_USERNAME, User } from "constants/userConstants"; import { ANONYMOUS_USERNAME, User } from "constants/userConstants";
import Button, { Size } from "components/ads/Button"; import Button, { Size } from "components/ads/Button";
import Icon, { IconSize } from "components/ads/Icon"; import Icon, { IconSize } from "components/ads/Icon";
@ -52,7 +52,6 @@ import { useLocation } from "react-router";
import { showConnectGitModal } from "actions/gitSyncActions"; import { showConnectGitModal } from "actions/gitSyncActions";
import RealtimeAppEditors from "./RealtimeAppEditors"; import RealtimeAppEditors from "./RealtimeAppEditors";
import { EditorSaveIndicator } from "./EditorSaveIndicator"; import { EditorSaveIndicator } from "./EditorSaveIndicator";
import getFeatureFlags from "utils/featureFlags";
import { retryPromise } from "utils/AppsmithUtils"; import { retryPromise } from "utils/AppsmithUtils";
import { fetchUsersForOrg } from "actions/orgActions"; import { fetchUsersForOrg } from "actions/orgActions";
@ -298,9 +297,11 @@ export function EditorHeader(props: EditorHeaderProps) {
showAppInviteUsersDialogSelector, showAppInviteUsersDialogSelector,
); );
const featureFlags = useSelector(selectFeatureFlags);
const handleClickDeploy = useCallback( const handleClickDeploy = useCallback(
(fromDeploy?: boolean) => { (fromDeploy?: boolean) => {
if (getFeatureFlags().GIT && isGitConnected) { if (featureFlags.GIT && isGitConnected) {
dispatch(showConnectGitModal()); dispatch(showConnectGitModal());
AnalyticsUtil.logEvent("GS_DEPLOY_GIT_CLICK", { AnalyticsUtil.logEvent("GS_DEPLOY_GIT_CLICK", {
source: fromDeploy source: fromDeploy
@ -311,7 +312,7 @@ export function EditorHeader(props: EditorHeaderProps) {
handlePublish(); handlePublish();
} }
}, },
[getFeatureFlags().GIT, dispatch, handlePublish], [featureFlags.GIT, dispatch, handlePublish],
); );
/** /**

View File

@ -32,7 +32,6 @@ import {
showConnectGitModal, showConnectGitModal,
} from "actions/gitSyncActions"; } from "actions/gitSyncActions";
import { GitSyncModalTab } from "entities/GitSync"; import { GitSyncModalTab } from "entities/GitSync";
import getFeatureFlags from "utils/featureFlags";
import { import {
getCountOfChangesToCommit, getCountOfChangesToCommit,
getGitStatus, getGitStatus,
@ -45,6 +44,7 @@ import SpinnerLoader from "pages/common/SpinnerLoader";
import { inGuidedTour } from "selectors/onboardingSelectors"; import { inGuidedTour } from "selectors/onboardingSelectors";
import Icon, { IconName, IconSize } from "components/ads/Icon"; import Icon, { IconName, IconSize } from "components/ads/Icon";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
import { selectFeatureFlags } from "selectors/usersSelectors";
type QuickActionButtonProps = { type QuickActionButtonProps = {
className?: string; className?: string;
@ -229,8 +229,9 @@ const PlaceholderButton = styled.div`
function ConnectGitPlaceholder() { function ConnectGitPlaceholder() {
const dispatch = useDispatch(); const dispatch = useDispatch();
const isInGuidedTour = useSelector(inGuidedTour); const isInGuidedTour = useSelector(inGuidedTour);
const featureFlags = useSelector(selectFeatureFlags);
const isTooltipEnabled = !getFeatureFlags().GIT || isInGuidedTour; const isTooltipEnabled = !featureFlags.GIT || isInGuidedTour;
const tooltipContent = !isInGuidedTour ? ( const tooltipContent = !isInGuidedTour ? (
<> <>
<div>{createMessage(NOT_LIVE_FOR_YOU_YET)}</div> <div>{createMessage(NOT_LIVE_FOR_YOU_YET)}</div>
@ -242,7 +243,7 @@ function ConnectGitPlaceholder() {
<div>{createMessage(DURING_ONBOARDING_TOUR)}</div> <div>{createMessage(DURING_ONBOARDING_TOUR)}</div>
</> </>
); );
const isGitConnectionEnabled = getFeatureFlags().GIT && !isInGuidedTour; const isGitConnectionEnabled = featureFlags.GIT && !isInGuidedTour;
return ( return (
<Container> <Container>
@ -297,6 +298,7 @@ export default function QuickGitActions() {
const isFetchingGitStatus = useSelector(getIsFetchingGitStatus); const isFetchingGitStatus = useSelector(getIsFetchingGitStatus);
const showPullLoadingState = isPullInProgress || isFetchingGitStatus; const showPullLoadingState = isPullInProgress || isFetchingGitStatus;
const changesToCommit = useSelector(getCountOfChangesToCommit); const changesToCommit = useSelector(getCountOfChangesToCommit);
const featureFlags = useSelector(selectFeatureFlags);
const quickActionButtons = getQuickActionButtons({ const quickActionButtons = getQuickActionButtons({
commit: () => { commit: () => {
@ -345,7 +347,7 @@ export default function QuickGitActions() {
showPullLoadingState, showPullLoadingState,
changesToCommit, changesToCommit,
}); });
return getFeatureFlags().GIT && isGitConnected ? ( return featureFlags.GIT && isGitConnected ? (
<Container> <Container>
<BranchButton /> <BranchButton />
{quickActionButtons.map((button) => ( {quickActionButtons.map((button) => (

View File

@ -9,7 +9,7 @@ export default function WithSuperUserHOC(
) { ) {
return function Wrapped(props: RouteComponentProps) { return function Wrapped(props: RouteComponentProps) {
const user = useSelector(getCurrentUser); const user = useSelector(getCurrentUser);
if (!user) return null;
if (!user?.isSuperUser || !user?.isConfigurable) { if (!user?.isSuperUser || !user?.isConfigurable) {
return <Redirect to={APPLICATIONS_URL} />; return <Redirect to={APPLICATIONS_URL} />;
} }

View File

@ -9,7 +9,7 @@ import { APPLICATIONS_URL, AUTH_LOGIN_URL } from "constants/routes";
export const requiresUnauth = (Component: React.ComponentType) => { export const requiresUnauth = (Component: React.ComponentType) => {
function Wrapped(props: any) { function Wrapped(props: any) {
const user = useSelector(getCurrentUser); const user = useSelector(getCurrentUser);
if (!user) return null;
if (user?.email && user?.email !== ANONYMOUS_USERNAME) { if (user?.email && user?.email !== ANONYMOUS_USERNAME) {
return <Redirect to={APPLICATIONS_URL} />; return <Redirect to={APPLICATIONS_URL} />;
} }
@ -22,7 +22,7 @@ export const requiresUnauth = (Component: React.ComponentType) => {
export const requiresAuth = (Component: React.ComponentType) => { export const requiresAuth = (Component: React.ComponentType) => {
return function Wrapped(props: any) { return function Wrapped(props: any) {
const user = useSelector(getCurrentUser); const user = useSelector(getCurrentUser);
if (!user) return null;
if (user?.email && user?.email !== ANONYMOUS_USERNAME) { if (user?.email && user?.email !== ANONYMOUS_USERNAME) {
return <Component {...props} />; return <Component {...props} />;
} }

View File

@ -7,10 +7,11 @@ import { Icon } from "@blueprintjs/core";
// import { Link } from "react-router-dom"; // import { Link } from "react-router-dom";
import General from "./General"; import General from "./General";
import { Colors } from "constants/Colors"; import { Colors } from "constants/Colors";
import getFeatureFlags from "utils/featureFlags";
import GitConfig from "./GitConfig"; import GitConfig from "./GitConfig";
import { useLocation } from "react-router"; import { useLocation } from "react-router";
import { GIT_PROFILE_ROUTE } from "constants/routes"; import { GIT_PROFILE_ROUTE } from "constants/routes";
import { useSelector } from "react-redux";
import { selectFeatureFlags } from "selectors/usersSelectors";
const ProfileWrapper = styled.div` const ProfileWrapper = styled.div`
width: ${(props) => props.theme.pageContentWidth}px; width: ${(props) => props.theme.pageContentWidth}px;
@ -32,6 +33,7 @@ const LinkToApplications = styled.div`
function UserProfile() { function UserProfile() {
const location = useLocation(); const location = useLocation();
const featureFlags = useSelector(selectFeatureFlags);
let initialTabIndex = 0; let initialTabIndex = 0;
const tabs: TabProp[] = [ const tabs: TabProp[] = [
@ -43,7 +45,7 @@ function UserProfile() {
}, },
]; ];
if (getFeatureFlags().GIT) { if (featureFlags.GIT) {
tabs.push({ tabs.push({
key: "gitConfig", key: "gitConfig",
title: "Git user config", title: "Git user config",

View File

@ -1,7 +1,7 @@
import React, { useState, useMemo, useEffect } from "react"; import React, { useState, useMemo, useEffect } from "react";
import { Link, useLocation } from "react-router-dom"; import { Link, useLocation } from "react-router-dom";
import { connect, useDispatch } from "react-redux"; import { connect, useDispatch, useSelector } from "react-redux";
import { getCurrentUser } from "selectors/usersSelectors"; import { getCurrentUser, selectFeatureFlags } from "selectors/usersSelectors";
import styled from "styled-components"; import styled from "styled-components";
import StyledHeader from "components/designSystems/appsmith/StyledHeader"; import StyledHeader from "components/designSystems/appsmith/StyledHeader";
import { ReactComponent as AppsmithLogo } from "assets/svg/appsmith_logo_primary.svg"; import { ReactComponent as AppsmithLogo } from "assets/svg/appsmith_logo_primary.svg";
@ -28,7 +28,6 @@ import { Indices } from "constants/Layers";
import Icon, { IconSize } from "components/ads/Icon"; import Icon, { IconSize } from "components/ads/Icon";
import { TemplatesTabItem } from "pages/Templates/TemplatesTabItem"; import { TemplatesTabItem } from "pages/Templates/TemplatesTabItem";
import { getTemplateNotificationSeenAction } from "actions/templateActions"; import { getTemplateNotificationSeenAction } from "actions/templateActions";
import getFeatureFlags from "utils/featureFlags";
const StyledPageHeader = styled(StyledHeader)<{ const StyledPageHeader = styled(StyledHeader)<{
hideShadow?: boolean; hideShadow?: boolean;
@ -131,6 +130,8 @@ export function PageHeader(props: PageHeaderProps) {
=${queryParams.get("redirectUrl")}`; =${queryParams.get("redirectUrl")}`;
} }
const featureFlags = useSelector(selectFeatureFlags);
useEffect(() => { useEffect(() => {
dispatch(getTemplateNotificationSeenAction()); dispatch(getTemplateNotificationSeenAction());
}, []); }, []);
@ -156,9 +157,9 @@ export function PageHeader(props: PageHeaderProps) {
const showTabs = useMemo(() => { const showTabs = useMemo(() => {
return ( return (
tabs.some((tab) => tab.matcher(location.pathname)) && tabs.some((tab) => tab.matcher(location.pathname)) &&
getFeatureFlags().APP_TEMPLATE !!featureFlags.APP_TEMPLATE
); );
}, [location.pathname]); }, [featureFlags, location.pathname]);
return ( return (
<StyledPageHeader <StyledPageHeader

View File

@ -11,6 +11,7 @@ import {
DefaultCurrentUserDetails, DefaultCurrentUserDetails,
User, User,
} from "constants/userConstants"; } from "constants/userConstants";
import FeatureFlags from "entities/FeatureFlags";
const initialState: UsersReduxState = { const initialState: UsersReduxState = {
loadingStates: { loadingStates: {
@ -22,7 +23,10 @@ const initialState: UsersReduxState = {
error: "", error: "",
current: undefined, current: undefined,
currentUser: undefined, currentUser: undefined,
featureFlagFetched: false, featureFlag: {
data: {},
isFetched: false,
},
}; };
const usersReducer = createReducer(initialState, { const usersReducer = createReducer(initialState, {
@ -155,15 +159,24 @@ const usersReducer = createReducer(initialState, {
photoId: action.payload.photoId, photoId: action.payload.photoId,
}, },
}), }),
[ReduxActionTypes.FETCH_FEATURE_FLAGS_SUCCESS]: (state: UsersReduxState) => ({ [ReduxActionTypes.FETCH_FEATURE_FLAGS_SUCCESS]: (
state: UsersReduxState,
action: ReduxAction<FeatureFlags>,
) => ({
...state, ...state,
featureFlagFetched: true, featureFlag: {
data: action.payload,
isFetched: true,
},
}), }),
[ReduxActionErrorTypes.FETCH_FEATURE_FLAGS_ERROR]: ( [ReduxActionErrorTypes.FETCH_FEATURE_FLAGS_ERROR]: (
state: UsersReduxState, state: UsersReduxState,
) => ({ ) => ({
...state, ...state,
featureFlagFetched: true, featureFlag: {
data: {},
isFetched: true,
},
}), }),
[ReduxActionTypes.UPDATE_USERS_COMMENTS_ONBOARDING_STATE]: ( [ReduxActionTypes.UPDATE_USERS_COMMENTS_ONBOARDING_STATE]: (
state: UsersReduxState, state: UsersReduxState,
@ -195,7 +208,10 @@ export interface UsersReduxState {
currentUser?: User; currentUser?: User;
error: string; error: string;
propPanePreferences?: PropertyPanePositionConfig; propPanePreferences?: PropertyPanePositionConfig;
featureFlagFetched: boolean; featureFlag: {
isFetched: boolean;
data: FeatureFlags;
};
} }
export default usersReducer; export default usersReducer;

View File

@ -38,6 +38,8 @@ import { getAppMode } from "selectors/applicationSelectors";
import { APP_MODE } from "entities/App"; import { APP_MODE } from "entities/App";
import { dataTreeTypeDefCreator } from "utils/autocomplete/dataTreeTypeDefCreator"; import { dataTreeTypeDefCreator } from "utils/autocomplete/dataTreeTypeDefCreator";
import TernServer from "utils/autocomplete/TernServer"; import TernServer from "utils/autocomplete/TernServer";
import { selectFeatureFlags } from "selectors/usersSelectors";
import FeatureFlags from "entities/FeatureFlags";
const getDebuggerErrors = (state: AppState) => state.ui.debugger.errors; const getDebuggerErrors = (state: AppState) => state.ui.debugger.errors;
/** /**
@ -301,7 +303,7 @@ export function* logSuccessfulBindings(
dataTree: DataTree, dataTree: DataTree,
evaluationOrder: string[], evaluationOrder: string[],
) { ) {
const appMode = yield select(getAppMode); const appMode: APP_MODE = yield select(getAppMode);
if (appMode === APP_MODE.PUBLISHED) return; if (appMode === APP_MODE.PUBLISHED) return;
if (!evaluationOrder) return; if (!evaluationOrder) return;
evaluationOrder.forEach((evaluatedPath) => { evaluationOrder.forEach((evaluatedPath) => {
@ -371,8 +373,10 @@ export function* updateTernDefinitions(
const treeWithoutPrivateWidgets = getDataTreeWithoutPrivateWidgets( const treeWithoutPrivateWidgets = getDataTreeWithoutPrivateWidgets(
dataTree, dataTree,
); );
const featureFlags: FeatureFlags = yield select(selectFeatureFlags);
const { def, entityInfo } = dataTreeTypeDefCreator( const { def, entityInfo } = dataTreeTypeDefCreator(
treeWithoutPrivateWidgets, treeWithoutPrivateWidgets,
!!featureFlags.JS_EDITOR,
); );
TernServer.updateDef("DATA_TREE", def, entityInfo); TernServer.updateDef("DATA_TREE", def, entityInfo);
const end = performance.now(); const end = performance.now();

View File

@ -3,13 +3,14 @@ import {
updateReflowOnBoardingAction, updateReflowOnBoardingAction,
} from "actions/reflowActions"; } from "actions/reflowActions";
import { import {
ReduxAction,
ReduxActionErrorTypes, ReduxActionErrorTypes,
ReduxActionTypes, ReduxActionTypes,
ReflowReduxActionTypes, ReflowReduxActionTypes,
} from "constants/ReduxActionConstants"; } from "constants/ReduxActionConstants";
import { User } from "constants/userConstants"; import { User } from "constants/userConstants";
import { isBoolean } from "lodash"; import { isBoolean } from "lodash";
import { all, put, select, takeLatest } from "redux-saga/effects"; import { all, put, select, takeLatest, take } from "redux-saga/effects";
import { getCurrentUser } from "selectors/usersSelectors"; import { getCurrentUser } from "selectors/usersSelectors";
import { import {
getReflowBetaFlag, getReflowBetaFlag,
@ -20,7 +21,13 @@ import {
function* initReflowStates() { function* initReflowStates() {
try { try {
const user: User = yield select(getCurrentUser); let user: User = yield select(getCurrentUser);
if (!user) {
const userFetched: ReduxAction<User> = yield take(
ReduxActionTypes.FETCH_USER_DETAILS_SUCCESS,
);
user = userFetched.payload;
}
const { email } = user; const { email } = user;
if (email) { if (email) {
const enableReflow: boolean = yield getReflowBetaFlag(email); const enableReflow: boolean = yield getReflowBetaFlag(email);
@ -62,8 +69,8 @@ function* closeReflowOnboardingCard() {
} }
export default function* reflowSagas() { export default function* reflowSagas() {
yield all([takeLatest(ReduxActionTypes.INITIALIZE_EDITOR, initReflowStates)]);
yield all([ yield all([
takeLatest(ReduxActionTypes.INITIALIZE_EDITOR, initReflowStates),
takeLatest( takeLatest(
ReflowReduxActionTypes.CLOSE_ONBOARDING_CARD, ReflowReduxActionTypes.CLOSE_ONBOARDING_CARD,
closeReflowOnboardingCard, closeReflowOnboardingCard,

View File

@ -1,4 +1,4 @@
import { call, all, spawn } from "redux-saga/effects"; import { call, all, spawn, race, take } from "redux-saga/effects";
import pageSagas from "sagas/PageSagas"; import pageSagas from "sagas/PageSagas";
import { watchActionSagas } from "./ActionSagas"; import { watchActionSagas } from "./ActionSagas";
import { watchJSActionSagas } from "./JSActionSagas"; import { watchJSActionSagas } from "./JSActionSagas";
@ -45,6 +45,7 @@ import * as sentry from "@sentry/react";
import formEvaluationChangeListener from "./FormEvaluationSaga"; import formEvaluationChangeListener from "./FormEvaluationSaga";
import SuperUserSagas from "./SuperUserSagas"; import SuperUserSagas from "./SuperUserSagas";
import reflowSagas from "./ReflowSagas"; import reflowSagas from "./ReflowSagas";
import { ReduxActionTypes } from "constants/ReduxActionConstants";
const sagas = [ const sagas = [
initSagas, initSagas,
@ -92,20 +93,26 @@ const sagas = [
reflowSagas, reflowSagas,
]; ];
export function* rootSaga(sagasToRun = sagas) { export function* rootSaga(sagasToRun = sagas): any {
yield all( // This race effect ensures that we fail as soon as the first safe crash is dispatched.
sagasToRun.map((saga) => // Without this, all the subsequent safe crash failures would be shown in the toast messages as well.
spawn(function*() { const result = yield race({
while (true) { running: all(
try { sagasToRun.map((saga) =>
yield call(saga); spawn(function*() {
break; while (true) {
} catch (e) { try {
log.error(e); yield call(saga);
sentry.captureException(e); break;
} catch (e) {
log.error(e);
sentry.captureException(e);
}
} }
} }),
}), ),
), ),
); crashed: take(ReduxActionTypes.SAFE_CRASH_APPSMITH),
});
if (result.crashed) yield call(rootSaga);
} }

View File

@ -15,12 +15,7 @@ import UserApi, {
UpdateUserRequest, UpdateUserRequest,
LeaveOrgRequest, LeaveOrgRequest,
} from "api/UserApi"; } from "api/UserApi";
import { import { AUTH_LOGIN_URL, SETUP } from "constants/routes";
APPLICATIONS_URL,
AUTH_LOGIN_URL,
BASE_URL,
SETUP,
} from "constants/routes";
import history from "utils/history"; import history from "utils/history";
import { ApiResponse } from "api/ApiResponses"; import { ApiResponse } from "api/ApiResponses";
import { import {
@ -37,7 +32,6 @@ import {
invitedUserSignupSuccess, invitedUserSignupSuccess,
fetchFeatureFlagsSuccess, fetchFeatureFlagsSuccess,
fetchFeatureFlagsError, fetchFeatureFlagsError,
fetchFeatureFlagsInit,
} from "actions/userActions"; } from "actions/userActions";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
import { INVITE_USERS_TO_ORG_FORM } from "constants/forms"; import { INVITE_USERS_TO_ORG_FORM } from "constants/forms";
@ -125,11 +119,6 @@ export function* getCurrentUserSaga() {
response.data.username !== ANONYMOUS_USERNAME response.data.username !== ANONYMOUS_USERNAME
) { ) {
enableTelemetry && AnalyticsUtil.identifyUser(response.data); enableTelemetry && AnalyticsUtil.identifyUser(response.data);
// make fetch feature call only if logged in
yield put(fetchFeatureFlagsInit());
} else {
// reset the flagsFetched flag
yield put(fetchFeatureFlagsSuccess());
} }
yield put({ yield put({
type: ReduxActionTypes.FETCH_USER_DETAILS_SUCCESS, type: ReduxActionTypes.FETCH_USER_DETAILS_SUCCESS,
@ -137,12 +126,6 @@ export function* getCurrentUserSaga() {
}); });
if (response.data.emptyInstance) { if (response.data.emptyInstance) {
history.replace(SETUP); history.replace(SETUP);
} else if (window.location.pathname === BASE_URL) {
if (response.data.isAnonymous) {
history.replace(AUTH_LOGIN_URL);
} else {
history.replace(APPLICATIONS_URL);
}
} }
PerformanceTracker.stopAsyncTracking( PerformanceTracker.stopAsyncTracking(
PerformanceTransactionName.USER_ME_API, PerformanceTransactionName.USER_ME_API,
@ -161,7 +144,7 @@ export function* getCurrentUserSaga() {
}); });
yield put({ yield put({
type: ReduxActionTypes.SAFE_CRASH_APPSMITH, type: ReduxActionTypes.SAFE_CRASH_APPSMITH_REQUEST,
payload: { payload: {
code: ERROR_CODES.SERVER_ERROR, code: ERROR_CODES.SERVER_ERROR,
}, },
@ -441,8 +424,7 @@ function* fetchFeatureFlags() {
const response: ApiResponse = yield call(UserApi.fetchFeatureFlags); const response: ApiResponse = yield call(UserApi.fetchFeatureFlags);
const isValidResponse: boolean = yield validateResponse(response); const isValidResponse: boolean = yield validateResponse(response);
if (isValidResponse) { if (isValidResponse) {
(window as any).FEATURE_FLAGS = response.data; yield put(fetchFeatureFlagsSuccess(response.data));
yield put(fetchFeatureFlagsSuccess());
} }
} catch (error) { } catch (error) {
log.error(error); log.error(error);

View File

@ -1,8 +1,7 @@
import { createSelector } from "reselect"; import { createSelector } from "reselect";
import { AppState } from "reducers"; import { AppState } from "reducers";
import { AppCollabReducerState } from "reducers/uiReducers/appCollabReducer"; import { AppCollabReducerState } from "reducers/uiReducers/appCollabReducer";
import { getCurrentUser } from "./usersSelectors"; import { getCurrentUser, selectFeatureFlags } from "./usersSelectors";
import getFeatureFlags from "../utils/featureFlags";
import { User } from "entities/AppCollab/CollabInterfaces"; import { User } from "entities/AppCollab/CollabInterfaces";
import { ANONYMOUS_USERNAME } from "constants/userConstants"; import { ANONYMOUS_USERNAME } from "constants/userConstants";
@ -15,7 +14,10 @@ export const getRealtimeAppEditors = createSelector(
appCollab.editors.filter((el) => el.email !== currentUser?.email), appCollab.editors.filter((el) => el.email !== currentUser?.email),
); );
export const isMultiplayerEnabledForUser = () => getFeatureFlags().MULTIPLAYER; export const isMultiplayerEnabledForUser = createSelector(
selectFeatureFlags,
(featureFlags) => featureFlags.MULTIPLAYER,
);
export const getConcurrentPageEditors = (state: AppState) => export const getConcurrentPageEditors = (state: AppState) =>
state.ui.appCollab.pageEditors; state.ui.appCollab.pageEditors;

View File

@ -21,9 +21,9 @@ import { JSCollectionDataState } from "reducers/entityReducers/jsActionsReducer"
import { JSCollection } from "entities/JSCollection"; import { JSCollection } from "entities/JSCollection";
import { DefaultPlugin, GenerateCRUDEnabledPluginMap } from "../api/PluginApi"; import { DefaultPlugin, GenerateCRUDEnabledPluginMap } from "../api/PluginApi";
import { APP_MODE } from "entities/App"; import { APP_MODE } from "entities/App";
import getFeatureFlags from "utils/featureFlags";
import { ExplorerFileEntity } from "pages/Editor/Explorer/helpers"; import { ExplorerFileEntity } from "pages/Editor/Explorer/helpers";
import { ActionValidationConfigMap } from "constants/PropertyControlConstants"; import { ActionValidationConfigMap } from "constants/PropertyControlConstants";
import { selectFeatureFlags } from "./usersSelectors";
export const getEntities = (state: AppState): AppState["entities"] => export const getEntities = (state: AppState): AppState["entities"] =>
state.entities; state.entities;
@ -663,8 +663,9 @@ export const selectFilesForExplorer = createSelector(
getActionsForCurrentPage, getActionsForCurrentPage,
getJSCollectionsForCurrentPage, getJSCollectionsForCurrentPage,
selectDatasourceIdToNameMap, selectDatasourceIdToNameMap,
(actions, jsActions, datasourceIdToNameMap) => { selectFeatureFlags,
const isJSEditorEnabled = getFeatureFlags().JS_EDITOR; (actions, jsActions, datasourceIdToNameMap, featureFlags) => {
const { JS_EDITOR: isJSEditorEnabled } = featureFlags;
const files = [...actions, ...(isJSEditorEnabled ? jsActions : [])].reduce( const files = [...actions, ...(isJSEditorEnabled ? jsActions : [])].reduce(
(acc, file) => { (acc, file) => {
let group = ""; let group = "";

View File

@ -11,4 +11,7 @@ export const getProppanePreference = (
state: AppState, state: AppState,
): PropertyPanePositionConfig | undefined => state.ui.users.propPanePreferences; ): PropertyPanePositionConfig | undefined => state.ui.users.propPanePreferences;
export const getFeatureFlagsFetched = (state: AppState) => export const getFeatureFlagsFetched = (state: AppState) =>
state.ui.users.featureFlagFetched; state.ui.users.featureFlag.isFetched;
export const selectFeatureFlags = (state: AppState) =>
state.ui.users.featureFlag.data;

View File

@ -15,7 +15,6 @@ import {
isWidget, isWidget,
} from "workers/evaluationUtils"; } from "workers/evaluationUtils";
import { DataTreeDefEntityInformation } from "utils/autocomplete/TernServer"; import { DataTreeDefEntityInformation } from "utils/autocomplete/TernServer";
import getFeatureFlags from "utils/featureFlags";
// When there is a complex data type, we store it in extra def and refer to it // When there is a complex data type, we store it in extra def and refer to it
// in the def // in the def
let extraDefs: any = {}; let extraDefs: any = {};
@ -27,12 +26,12 @@ let extraDefs: any = {};
// or DATA_TREE.ACTION.ACTION.Api1 // or DATA_TREE.ACTION.ACTION.Api1
export const dataTreeTypeDefCreator = ( export const dataTreeTypeDefCreator = (
dataTree: DataTree, dataTree: DataTree,
isJSEditorEnabled: boolean,
): { def: Def; entityInfo: Map<string, DataTreeDefEntityInformation> } => { ): { def: Def; entityInfo: Map<string, DataTreeDefEntityInformation> } => {
const def: any = { const def: any = {
"!name": "DATA_TREE", "!name": "DATA_TREE",
}; };
const entityMap: Map<string, DataTreeDefEntityInformation> = new Map(); const entityMap: Map<string, DataTreeDefEntityInformation> = new Map();
const isJSEditorEnabled = getFeatureFlags().JS_EDITOR;
Object.entries(dataTree).forEach(([entityName, entity]) => { Object.entries(dataTree).forEach(([entityName, entity]) => {
if (isWidget(entity)) { if (isWidget(entity)) {
const widgetType = entity.type; const widgetType = entity.type;

View File

@ -1,5 +0,0 @@
import FeatureFlag from "entities/FeatureFlag";
export default function getFeatureFlags(): FeatureFlag {
return (window as any).FEATURE_FLAGS || {};
}

View File

@ -121,6 +121,7 @@ public class SecurityConfig {
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, USER_URL + "/invite/verify"), ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, USER_URL + "/invite/verify"),
ServerWebExchangeMatchers.pathMatchers(HttpMethod.PUT, USER_URL + "/invite/confirm"), ServerWebExchangeMatchers.pathMatchers(HttpMethod.PUT, USER_URL + "/invite/confirm"),
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, USER_URL + "/me"), ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, USER_URL + "/me"),
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, USER_URL + "/features"),
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, ACTION_URL + "/**"), ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, ACTION_URL + "/**"),
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, ACTION_COLLECTION_URL + "/view"), ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, ACTION_COLLECTION_URL + "/view"),
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, PAGE_URL + "/**"), ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, PAGE_URL + "/**"),

View File

@ -52,6 +52,10 @@ public class FeatureFlagServiceCEImpl implements FeatureFlagServiceCE {
.flatMap(featureName -> Mono.just(featureName).zipWith(currentUser)); .flatMap(featureName -> Mono.just(featureName).zipWith(currentUser));
return featureUserTuple return featureUserTuple
.collectMap(tuple -> tuple.getT1(), tuple -> check(tuple.getT1(), tuple.getT2())); .filter(objects -> !objects.getT2().isAnonymous())
.collectMap(
Tuple2::getT1,
tuple -> check(tuple.getT1(), tuple.getT2())
);
} }
} }