chore: Parallelise feature flags and user info API (#12263)
This commit is contained in:
parent
461f35380b
commit
fabfb65a4f
|
|
@ -48,14 +48,15 @@ import { trimTrailingSlash } from "utils/helpers";
|
|||
import { getSafeCrash, getSafeCrashCode } from "selectors/errorSelectors";
|
||||
import UserProfile from "pages/UserProfile";
|
||||
import { getCurrentUser } from "actions/authActions";
|
||||
import { getFeatureFlagsFetched } from "selectors/usersSelectors";
|
||||
import { selectFeatureFlags } from "selectors/usersSelectors";
|
||||
import Setup from "pages/setup";
|
||||
import Settings from "pages/Settings";
|
||||
import SignupSuccess from "pages/setup/SignupSuccess";
|
||||
import { Theme } from "constants/DefaultTheme";
|
||||
import { ERROR_CODES } from "ce/constants/ApiConstants";
|
||||
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);
|
||||
|
||||
|
|
@ -78,19 +79,21 @@ function changeAppBackground(currentTheme: any) {
|
|||
function AppRouter(props: {
|
||||
safeCrash: boolean;
|
||||
getCurrentUser: () => void;
|
||||
getFeatureFlags: () => void;
|
||||
currentTheme: Theme;
|
||||
featureFlagsFetched: boolean;
|
||||
safeCrashCode?: ERROR_CODES;
|
||||
featureFlags: FeatureFlags;
|
||||
setTheme: (theme: ThemeMode) => void;
|
||||
}) {
|
||||
const { featureFlags, getCurrentUser, getFeatureFlags } = props;
|
||||
useEffect(() => {
|
||||
AnalyticsUtil.logEvent("ROUTE_CHANGE", { path: window.location.pathname });
|
||||
const stopListener = history.listen((location: any) => {
|
||||
AnalyticsUtil.logEvent("ROUTE_CHANGE", { path: location.pathname });
|
||||
changeAppBackground(props.currentTheme);
|
||||
});
|
||||
props.getCurrentUser();
|
||||
|
||||
getCurrentUser();
|
||||
getFeatureFlags();
|
||||
return stopListener;
|
||||
}, []);
|
||||
|
||||
|
|
@ -98,8 +101,6 @@ function AppRouter(props: {
|
|||
changeAppBackground(props.currentTheme);
|
||||
}, [props.currentTheme]);
|
||||
|
||||
if (!props.featureFlagsFetched) return null;
|
||||
|
||||
return (
|
||||
<Router history={history}>
|
||||
<Suspense fallback={loadingIndicator}>
|
||||
|
|
@ -128,14 +129,13 @@ function AppRouter(props: {
|
|||
exact
|
||||
path={SIGNUP_SUCCESS_URL}
|
||||
/>
|
||||
|
||||
<SentryRoute component={UserProfile} path={PROFILE} />
|
||||
<SentryRoute
|
||||
component={UnsubscribeEmail}
|
||||
path={UNSUBSCRIBE_EMAIL_URL}
|
||||
/>
|
||||
<SentryRoute component={Setup} exact path={SETUP} />
|
||||
{getFeatureFlags().APP_TEMPLATE && (
|
||||
{featureFlags.APP_TEMPLATE && (
|
||||
<SentryRoute
|
||||
component={TemplatesListLoader}
|
||||
path={TEMPLATES_PATH}
|
||||
|
|
@ -174,7 +174,7 @@ const mapStateToProps = (state: AppState) => ({
|
|||
currentTheme: getCurrentThemeDetails(state),
|
||||
safeCrash: getSafeCrash(state),
|
||||
safeCrashCode: getSafeCrashCode(state),
|
||||
featureFlagsFetched: getFeatureFlagsFetched(state),
|
||||
featureFlags: selectFeatureFlags(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: any) => ({
|
||||
|
|
@ -182,6 +182,7 @@ const mapDispatchToProps = (dispatch: any) => ({
|
|||
dispatch(setThemeMode(mode));
|
||||
},
|
||||
getCurrentUser: () => dispatch(getCurrentUser()),
|
||||
getFeatureFlags: () => dispatch(fetchFeatureFlagsInit()),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AppRouter);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
UpdateUserRequest,
|
||||
VerifyTokenRequest,
|
||||
} from "api/UserApi";
|
||||
import FeatureFlags from "entities/FeatureFlags";
|
||||
|
||||
export const logoutUser = (payload?: { redirectURL: string }) => ({
|
||||
type: ReduxActionTypes.LOGOUT_USER_INIT,
|
||||
|
|
@ -108,8 +109,9 @@ export const fetchFeatureFlagsInit = () => ({
|
|||
type: ReduxActionTypes.FETCH_FEATURE_FLAGS_INIT,
|
||||
});
|
||||
|
||||
export const fetchFeatureFlagsSuccess = () => ({
|
||||
export const fetchFeatureFlagsSuccess = (payload: FeatureFlags) => ({
|
||||
type: ReduxActionTypes.FETCH_FEATURE_FLAGS_SUCCESS,
|
||||
payload,
|
||||
});
|
||||
|
||||
export const fetchFeatureFlagsError = (error: any) => ({
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import { Icon, Popover, PopoverPosition } from "@blueprintjs/core";
|
|||
import { Theme } from "constants/DefaultTheme";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { getIsGitConnected } from "../../../../selectors/gitSyncSelectors";
|
||||
import getFeatureFlags from "utils/featureFlags";
|
||||
import { setIsGitSyncModalOpen } from "actions/gitSyncActions";
|
||||
import { GitSyncModalTab } from "entities/GitSync";
|
||||
import { Colors } from "constants/Colors";
|
||||
|
||||
import { ReactComponent as GitBranch } from "assets/icons/ads/git-branch.svg";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { selectFeatureFlags } from "selectors/usersSelectors";
|
||||
|
||||
const DeployLinkDialog = styled.div`
|
||||
flex-direction: column;
|
||||
|
|
@ -74,6 +74,8 @@ export const DeployLinkButton = withTheme((props: Props) => {
|
|||
|
||||
const isGitConnected = useSelector(getIsGitConnected);
|
||||
|
||||
const featureFlags = useSelector(selectFeatureFlags);
|
||||
|
||||
const onClose = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
|
@ -96,7 +98,7 @@ export const DeployLinkButton = withTheme((props: Props) => {
|
|||
canEscapeKeyClose={false}
|
||||
content={
|
||||
<DeployLinkDialog>
|
||||
{getFeatureFlags().GIT && !isGitConnected && (
|
||||
{featureFlags.GIT && !isGitConnected && (
|
||||
<DeployLink
|
||||
className="t--connect-to-git-btn"
|
||||
onClick={goToGitConnectionPopup}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
|||
import { getEntityNameAndPropertyPath } from "workers/evaluationUtils";
|
||||
import { JSCollectionData } from "reducers/entityReducers/jsActionsReducer";
|
||||
import { createNewJSCollection } from "actions/jsPaneActions";
|
||||
import getFeatureFlags from "utils/featureFlags";
|
||||
import { JSAction, Variable } from "entities/JSCollection";
|
||||
import {
|
||||
CLEAR_INTERVAL,
|
||||
|
|
@ -60,10 +59,11 @@ import {
|
|||
import { toggleShowGlobalSearchModal } from "actions/globalSearchActions";
|
||||
import { filterCategories, SEARCH_CATEGORY_ID } from "../GlobalSearch/utils";
|
||||
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
||||
import { selectFeatureFlags } from "selectors/usersSelectors";
|
||||
import FeatureFlags from "entities/FeatureFlags";
|
||||
|
||||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
/* 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 }[] = [
|
||||
{
|
||||
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) {
|
||||
const jsOption = baseOptions.find(
|
||||
(option: any) => option.value === ActionType.jsFunction,
|
||||
|
|
@ -413,8 +414,9 @@ function getIntegrationOptionsWithChildren(
|
|||
jsActions: Array<JSCollectionData>,
|
||||
createIntegrationOption: TreeDropdownOption,
|
||||
dispatch: any,
|
||||
featureFlags: FeatureFlags,
|
||||
) {
|
||||
const isJSEditorEnabled = getFeatureFlags().JS_EDITOR;
|
||||
const { JS_EDITOR: isJSEditorEnabled } = featureFlags;
|
||||
const createJSObject: TreeDropdownOption = {
|
||||
label: "New JS Object",
|
||||
value: "JSObject",
|
||||
|
|
@ -527,6 +529,7 @@ function getIntegrationOptionsWithChildren(
|
|||
function useIntegrationsOptionTree() {
|
||||
const pageId = useSelector(getCurrentPageId) || "";
|
||||
const applicationId = useSelector(getCurrentApplicationId) as string;
|
||||
const featureFlags = useSelector(selectFeatureFlags);
|
||||
const dispatch = useDispatch();
|
||||
const plugins = useSelector((state: AppState) => {
|
||||
return state.entities.plugins.list;
|
||||
|
|
@ -539,7 +542,7 @@ function useIntegrationsOptionTree() {
|
|||
pageId,
|
||||
applicationId,
|
||||
pluginGroups,
|
||||
getBaseOptions(),
|
||||
getBaseOptions(featureFlags),
|
||||
actions,
|
||||
jsActions,
|
||||
{
|
||||
|
|
@ -557,6 +560,7 @@ function useIntegrationsOptionTree() {
|
|||
},
|
||||
},
|
||||
dispatch,
|
||||
featureFlags,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
type FeatureFlag = {
|
||||
APP_TEMPLATE: boolean;
|
||||
JS_EDITOR: boolean;
|
||||
MULTIPLAYER: boolean;
|
||||
SNIPPET: boolean;
|
||||
GIT: boolean;
|
||||
GIT_IMPORT: boolean;
|
||||
};
|
||||
|
||||
export default FeatureFlag;
|
||||
10
app/client/src/entities/FeatureFlags.ts
Normal file
10
app/client/src/entities/FeatureFlags.ts
Normal 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;
|
||||
|
|
@ -27,7 +27,7 @@ import { getIsImportingApplication } from "selectors/applicationSelectors";
|
|||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
import Dialog from "../../components/ads/DialogComponent";
|
||||
import { Classes } from "@blueprintjs/core";
|
||||
import getFeatureFlags from "../../utils/featureFlags";
|
||||
import { selectFeatureFlags } from "selectors/usersSelectors";
|
||||
|
||||
const StyledDialog = styled(Dialog)`
|
||||
&& .${Classes.DIALOG_HEADER} {
|
||||
|
|
@ -259,7 +259,9 @@ function ImportApplicationModal(props: ImportApplicationModalProps) {
|
|||
}, [appFileToBeUploaded, importingApplication]);
|
||||
|
||||
const onRemoveFile = useCallback(() => setAppFileToBeUploaded(null), []);
|
||||
const isGitImportFeatureEnabled = getFeatureFlags().GIT_IMPORT;
|
||||
|
||||
const featureFlags = useSelector(selectFeatureFlags);
|
||||
const { GIT_IMPORT: isGitImportFeatureEnabled } = featureFlags;
|
||||
|
||||
return (
|
||||
<StyledDialog
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import { isPermitted, PERMISSION_TYPE } from "./permissionHelpers";
|
|||
import FormDialogComponent from "components/editorComponents/form/FormDialogComponent";
|
||||
import Dialog from "components/ads/DialogComponent";
|
||||
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 {
|
||||
DropdownOnSelectActions,
|
||||
|
|
@ -85,7 +85,6 @@ import {
|
|||
import { ReactComponent as NoAppsFoundIcon } from "assets/svg/no-apps-icon.svg";
|
||||
|
||||
import { setHeaderMeta } from "actions/themeActions";
|
||||
import getFeatureFlags from "utils/featureFlags";
|
||||
import SharedUserList from "pages/common/SharedUserList";
|
||||
import { useIsMobileDevice } from "utils/hooks/useDeviceDetect";
|
||||
import { Indices } from "constants/Layers";
|
||||
|
|
@ -523,6 +522,7 @@ function ApplicationsSection(props: any) {
|
|||
) => {
|
||||
dispatch(updateApplication(id, data));
|
||||
};
|
||||
const featureFlags = useSelector(selectFeatureFlags);
|
||||
|
||||
useEffect(() => {
|
||||
// Clears URL params cache
|
||||
|
|
@ -931,7 +931,7 @@ function ApplicationsSection(props: any) {
|
|||
isMobile={isMobile}
|
||||
>
|
||||
{organizationsListComponent}
|
||||
{getFeatureFlags().GIT_IMPORT && <GitSyncModal isImport />}
|
||||
{featureFlags.GIT_IMPORT && <GitSyncModal isImport />}
|
||||
<ReconnectDatasourceModal />
|
||||
</ApplicationContainer>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import {
|
|||
} from "../../Applications/permissionHelpers";
|
||||
import { getCurrentApplication } from "selectors/applicationSelectors";
|
||||
import { Colors } from "constants/Colors";
|
||||
import getFeatureFlags from "utils/featureFlags";
|
||||
import { setIsGitSyncModalOpen } from "actions/gitSyncActions";
|
||||
import { GitSyncModalTab } from "entities/GitSync";
|
||||
import { getIsGitConnected } from "selectors/gitSyncSelectors";
|
||||
|
|
@ -38,6 +37,7 @@ import { redoAction, undoAction } from "actions/pageActions";
|
|||
import { redoShortCut, undoShortCut } from "utils/helpers";
|
||||
import { pageListEditorURL } from "RouteBuilder";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { selectFeatureFlags } from "selectors/usersSelectors";
|
||||
|
||||
type NavigationMenuDataProps = ThemeProp & {
|
||||
editMode: typeof noop;
|
||||
|
|
@ -122,7 +122,9 @@ export const GetNavigationMenuData = ({
|
|||
},
|
||||
];
|
||||
|
||||
if (getFeatureFlags().GIT && !isGitConnected) {
|
||||
const featureFlags = useSelector(selectFeatureFlags);
|
||||
|
||||
if (featureFlags.GIT && !isGitConnected) {
|
||||
deployOptions.push({
|
||||
text: createMessage(CONNECT_TO_GIT_OPTION),
|
||||
onClick: () => openGitConnectionPopup(),
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import {
|
|||
} from "selectors/applicationSelectors";
|
||||
import EditorAppName from "./EditorAppName";
|
||||
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 Button, { Size } from "components/ads/Button";
|
||||
import Icon, { IconSize } from "components/ads/Icon";
|
||||
|
|
@ -52,7 +52,6 @@ import { useLocation } from "react-router";
|
|||
import { showConnectGitModal } from "actions/gitSyncActions";
|
||||
import RealtimeAppEditors from "./RealtimeAppEditors";
|
||||
import { EditorSaveIndicator } from "./EditorSaveIndicator";
|
||||
import getFeatureFlags from "utils/featureFlags";
|
||||
|
||||
import { retryPromise } from "utils/AppsmithUtils";
|
||||
import { fetchUsersForOrg } from "actions/orgActions";
|
||||
|
|
@ -298,9 +297,11 @@ export function EditorHeader(props: EditorHeaderProps) {
|
|||
showAppInviteUsersDialogSelector,
|
||||
);
|
||||
|
||||
const featureFlags = useSelector(selectFeatureFlags);
|
||||
|
||||
const handleClickDeploy = useCallback(
|
||||
(fromDeploy?: boolean) => {
|
||||
if (getFeatureFlags().GIT && isGitConnected) {
|
||||
if (featureFlags.GIT && isGitConnected) {
|
||||
dispatch(showConnectGitModal());
|
||||
AnalyticsUtil.logEvent("GS_DEPLOY_GIT_CLICK", {
|
||||
source: fromDeploy
|
||||
|
|
@ -311,7 +312,7 @@ export function EditorHeader(props: EditorHeaderProps) {
|
|||
handlePublish();
|
||||
}
|
||||
},
|
||||
[getFeatureFlags().GIT, dispatch, handlePublish],
|
||||
[featureFlags.GIT, dispatch, handlePublish],
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import {
|
|||
showConnectGitModal,
|
||||
} from "actions/gitSyncActions";
|
||||
import { GitSyncModalTab } from "entities/GitSync";
|
||||
import getFeatureFlags from "utils/featureFlags";
|
||||
import {
|
||||
getCountOfChangesToCommit,
|
||||
getGitStatus,
|
||||
|
|
@ -45,6 +44,7 @@ import SpinnerLoader from "pages/common/SpinnerLoader";
|
|||
import { inGuidedTour } from "selectors/onboardingSelectors";
|
||||
import Icon, { IconName, IconSize } from "components/ads/Icon";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { selectFeatureFlags } from "selectors/usersSelectors";
|
||||
|
||||
type QuickActionButtonProps = {
|
||||
className?: string;
|
||||
|
|
@ -229,8 +229,9 @@ const PlaceholderButton = styled.div`
|
|||
function ConnectGitPlaceholder() {
|
||||
const dispatch = useDispatch();
|
||||
const isInGuidedTour = useSelector(inGuidedTour);
|
||||
const featureFlags = useSelector(selectFeatureFlags);
|
||||
|
||||
const isTooltipEnabled = !getFeatureFlags().GIT || isInGuidedTour;
|
||||
const isTooltipEnabled = !featureFlags.GIT || isInGuidedTour;
|
||||
const tooltipContent = !isInGuidedTour ? (
|
||||
<>
|
||||
<div>{createMessage(NOT_LIVE_FOR_YOU_YET)}</div>
|
||||
|
|
@ -242,7 +243,7 @@ function ConnectGitPlaceholder() {
|
|||
<div>{createMessage(DURING_ONBOARDING_TOUR)}</div>
|
||||
</>
|
||||
);
|
||||
const isGitConnectionEnabled = getFeatureFlags().GIT && !isInGuidedTour;
|
||||
const isGitConnectionEnabled = featureFlags.GIT && !isInGuidedTour;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
|
|
@ -297,6 +298,7 @@ export default function QuickGitActions() {
|
|||
const isFetchingGitStatus = useSelector(getIsFetchingGitStatus);
|
||||
const showPullLoadingState = isPullInProgress || isFetchingGitStatus;
|
||||
const changesToCommit = useSelector(getCountOfChangesToCommit);
|
||||
const featureFlags = useSelector(selectFeatureFlags);
|
||||
|
||||
const quickActionButtons = getQuickActionButtons({
|
||||
commit: () => {
|
||||
|
|
@ -345,7 +347,7 @@ export default function QuickGitActions() {
|
|||
showPullLoadingState,
|
||||
changesToCommit,
|
||||
});
|
||||
return getFeatureFlags().GIT && isGitConnected ? (
|
||||
return featureFlags.GIT && isGitConnected ? (
|
||||
<Container>
|
||||
<BranchButton />
|
||||
{quickActionButtons.map((button) => (
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export default function WithSuperUserHOC(
|
|||
) {
|
||||
return function Wrapped(props: RouteComponentProps) {
|
||||
const user = useSelector(getCurrentUser);
|
||||
|
||||
if (!user) return null;
|
||||
if (!user?.isSuperUser || !user?.isConfigurable) {
|
||||
return <Redirect to={APPLICATIONS_URL} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { APPLICATIONS_URL, AUTH_LOGIN_URL } from "constants/routes";
|
|||
export const requiresUnauth = (Component: React.ComponentType) => {
|
||||
function Wrapped(props: any) {
|
||||
const user = useSelector(getCurrentUser);
|
||||
|
||||
if (!user) return null;
|
||||
if (user?.email && user?.email !== ANONYMOUS_USERNAME) {
|
||||
return <Redirect to={APPLICATIONS_URL} />;
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ export const requiresUnauth = (Component: React.ComponentType) => {
|
|||
export const requiresAuth = (Component: React.ComponentType) => {
|
||||
return function Wrapped(props: any) {
|
||||
const user = useSelector(getCurrentUser);
|
||||
|
||||
if (!user) return null;
|
||||
if (user?.email && user?.email !== ANONYMOUS_USERNAME) {
|
||||
return <Component {...props} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@ import { Icon } from "@blueprintjs/core";
|
|||
// import { Link } from "react-router-dom";
|
||||
import General from "./General";
|
||||
import { Colors } from "constants/Colors";
|
||||
import getFeatureFlags from "utils/featureFlags";
|
||||
import GitConfig from "./GitConfig";
|
||||
import { useLocation } from "react-router";
|
||||
import { GIT_PROFILE_ROUTE } from "constants/routes";
|
||||
import { useSelector } from "react-redux";
|
||||
import { selectFeatureFlags } from "selectors/usersSelectors";
|
||||
|
||||
const ProfileWrapper = styled.div`
|
||||
width: ${(props) => props.theme.pageContentWidth}px;
|
||||
|
|
@ -32,6 +33,7 @@ const LinkToApplications = styled.div`
|
|||
|
||||
function UserProfile() {
|
||||
const location = useLocation();
|
||||
const featureFlags = useSelector(selectFeatureFlags);
|
||||
|
||||
let initialTabIndex = 0;
|
||||
const tabs: TabProp[] = [
|
||||
|
|
@ -43,7 +45,7 @@ function UserProfile() {
|
|||
},
|
||||
];
|
||||
|
||||
if (getFeatureFlags().GIT) {
|
||||
if (featureFlags.GIT) {
|
||||
tabs.push({
|
||||
key: "gitConfig",
|
||||
title: "Git user config",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState, useMemo, useEffect } from "react";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { connect, useDispatch } from "react-redux";
|
||||
import { getCurrentUser } from "selectors/usersSelectors";
|
||||
import { connect, useDispatch, useSelector } from "react-redux";
|
||||
import { getCurrentUser, selectFeatureFlags } from "selectors/usersSelectors";
|
||||
import styled from "styled-components";
|
||||
import StyledHeader from "components/designSystems/appsmith/StyledHeader";
|
||||
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 { TemplatesTabItem } from "pages/Templates/TemplatesTabItem";
|
||||
import { getTemplateNotificationSeenAction } from "actions/templateActions";
|
||||
import getFeatureFlags from "utils/featureFlags";
|
||||
|
||||
const StyledPageHeader = styled(StyledHeader)<{
|
||||
hideShadow?: boolean;
|
||||
|
|
@ -131,6 +130,8 @@ export function PageHeader(props: PageHeaderProps) {
|
|||
=${queryParams.get("redirectUrl")}`;
|
||||
}
|
||||
|
||||
const featureFlags = useSelector(selectFeatureFlags);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getTemplateNotificationSeenAction());
|
||||
}, []);
|
||||
|
|
@ -156,9 +157,9 @@ export function PageHeader(props: PageHeaderProps) {
|
|||
const showTabs = useMemo(() => {
|
||||
return (
|
||||
tabs.some((tab) => tab.matcher(location.pathname)) &&
|
||||
getFeatureFlags().APP_TEMPLATE
|
||||
!!featureFlags.APP_TEMPLATE
|
||||
);
|
||||
}, [location.pathname]);
|
||||
}, [featureFlags, location.pathname]);
|
||||
|
||||
return (
|
||||
<StyledPageHeader
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
DefaultCurrentUserDetails,
|
||||
User,
|
||||
} from "constants/userConstants";
|
||||
import FeatureFlags from "entities/FeatureFlags";
|
||||
|
||||
const initialState: UsersReduxState = {
|
||||
loadingStates: {
|
||||
|
|
@ -22,7 +23,10 @@ const initialState: UsersReduxState = {
|
|||
error: "",
|
||||
current: undefined,
|
||||
currentUser: undefined,
|
||||
featureFlagFetched: false,
|
||||
featureFlag: {
|
||||
data: {},
|
||||
isFetched: false,
|
||||
},
|
||||
};
|
||||
|
||||
const usersReducer = createReducer(initialState, {
|
||||
|
|
@ -155,15 +159,24 @@ const usersReducer = createReducer(initialState, {
|
|||
photoId: action.payload.photoId,
|
||||
},
|
||||
}),
|
||||
[ReduxActionTypes.FETCH_FEATURE_FLAGS_SUCCESS]: (state: UsersReduxState) => ({
|
||||
[ReduxActionTypes.FETCH_FEATURE_FLAGS_SUCCESS]: (
|
||||
state: UsersReduxState,
|
||||
action: ReduxAction<FeatureFlags>,
|
||||
) => ({
|
||||
...state,
|
||||
featureFlagFetched: true,
|
||||
featureFlag: {
|
||||
data: action.payload,
|
||||
isFetched: true,
|
||||
},
|
||||
}),
|
||||
[ReduxActionErrorTypes.FETCH_FEATURE_FLAGS_ERROR]: (
|
||||
state: UsersReduxState,
|
||||
) => ({
|
||||
...state,
|
||||
featureFlagFetched: true,
|
||||
featureFlag: {
|
||||
data: {},
|
||||
isFetched: true,
|
||||
},
|
||||
}),
|
||||
[ReduxActionTypes.UPDATE_USERS_COMMENTS_ONBOARDING_STATE]: (
|
||||
state: UsersReduxState,
|
||||
|
|
@ -195,7 +208,10 @@ export interface UsersReduxState {
|
|||
currentUser?: User;
|
||||
error: string;
|
||||
propPanePreferences?: PropertyPanePositionConfig;
|
||||
featureFlagFetched: boolean;
|
||||
featureFlag: {
|
||||
isFetched: boolean;
|
||||
data: FeatureFlags;
|
||||
};
|
||||
}
|
||||
|
||||
export default usersReducer;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ import { getAppMode } from "selectors/applicationSelectors";
|
|||
import { APP_MODE } from "entities/App";
|
||||
import { dataTreeTypeDefCreator } from "utils/autocomplete/dataTreeTypeDefCreator";
|
||||
import TernServer from "utils/autocomplete/TernServer";
|
||||
import { selectFeatureFlags } from "selectors/usersSelectors";
|
||||
import FeatureFlags from "entities/FeatureFlags";
|
||||
|
||||
const getDebuggerErrors = (state: AppState) => state.ui.debugger.errors;
|
||||
/**
|
||||
|
|
@ -301,7 +303,7 @@ export function* logSuccessfulBindings(
|
|||
dataTree: DataTree,
|
||||
evaluationOrder: string[],
|
||||
) {
|
||||
const appMode = yield select(getAppMode);
|
||||
const appMode: APP_MODE = yield select(getAppMode);
|
||||
if (appMode === APP_MODE.PUBLISHED) return;
|
||||
if (!evaluationOrder) return;
|
||||
evaluationOrder.forEach((evaluatedPath) => {
|
||||
|
|
@ -371,8 +373,10 @@ export function* updateTernDefinitions(
|
|||
const treeWithoutPrivateWidgets = getDataTreeWithoutPrivateWidgets(
|
||||
dataTree,
|
||||
);
|
||||
const featureFlags: FeatureFlags = yield select(selectFeatureFlags);
|
||||
const { def, entityInfo } = dataTreeTypeDefCreator(
|
||||
treeWithoutPrivateWidgets,
|
||||
!!featureFlags.JS_EDITOR,
|
||||
);
|
||||
TernServer.updateDef("DATA_TREE", def, entityInfo);
|
||||
const end = performance.now();
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@ import {
|
|||
updateReflowOnBoardingAction,
|
||||
} from "actions/reflowActions";
|
||||
import {
|
||||
ReduxAction,
|
||||
ReduxActionErrorTypes,
|
||||
ReduxActionTypes,
|
||||
ReflowReduxActionTypes,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { User } from "constants/userConstants";
|
||||
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 {
|
||||
getReflowBetaFlag,
|
||||
|
|
@ -20,7 +21,13 @@ import {
|
|||
|
||||
function* initReflowStates() {
|
||||
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;
|
||||
if (email) {
|
||||
const enableReflow: boolean = yield getReflowBetaFlag(email);
|
||||
|
|
@ -62,8 +69,8 @@ function* closeReflowOnboardingCard() {
|
|||
}
|
||||
|
||||
export default function* reflowSagas() {
|
||||
yield all([takeLatest(ReduxActionTypes.INITIALIZE_EDITOR, initReflowStates)]);
|
||||
yield all([
|
||||
takeLatest(ReduxActionTypes.INITIALIZE_EDITOR, initReflowStates),
|
||||
takeLatest(
|
||||
ReflowReduxActionTypes.CLOSE_ONBOARDING_CARD,
|
||||
closeReflowOnboardingCard,
|
||||
|
|
|
|||
|
|
@ -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 { watchActionSagas } from "./ActionSagas";
|
||||
import { watchJSActionSagas } from "./JSActionSagas";
|
||||
|
|
@ -45,6 +45,7 @@ import * as sentry from "@sentry/react";
|
|||
import formEvaluationChangeListener from "./FormEvaluationSaga";
|
||||
import SuperUserSagas from "./SuperUserSagas";
|
||||
import reflowSagas from "./ReflowSagas";
|
||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
|
||||
const sagas = [
|
||||
initSagas,
|
||||
|
|
@ -92,20 +93,26 @@ const sagas = [
|
|||
reflowSagas,
|
||||
];
|
||||
|
||||
export function* rootSaga(sagasToRun = sagas) {
|
||||
yield all(
|
||||
sagasToRun.map((saga) =>
|
||||
spawn(function*() {
|
||||
while (true) {
|
||||
try {
|
||||
yield call(saga);
|
||||
break;
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
sentry.captureException(e);
|
||||
export function* rootSaga(sagasToRun = sagas): any {
|
||||
// This race effect ensures that we fail as soon as the first safe crash is dispatched.
|
||||
// Without this, all the subsequent safe crash failures would be shown in the toast messages as well.
|
||||
const result = yield race({
|
||||
running: all(
|
||||
sagasToRun.map((saga) =>
|
||||
spawn(function*() {
|
||||
while (true) {
|
||||
try {
|
||||
yield call(saga);
|
||||
break;
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
sentry.captureException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
crashed: take(ReduxActionTypes.SAFE_CRASH_APPSMITH),
|
||||
});
|
||||
if (result.crashed) yield call(rootSaga);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,12 +15,7 @@ import UserApi, {
|
|||
UpdateUserRequest,
|
||||
LeaveOrgRequest,
|
||||
} from "api/UserApi";
|
||||
import {
|
||||
APPLICATIONS_URL,
|
||||
AUTH_LOGIN_URL,
|
||||
BASE_URL,
|
||||
SETUP,
|
||||
} from "constants/routes";
|
||||
import { AUTH_LOGIN_URL, SETUP } from "constants/routes";
|
||||
import history from "utils/history";
|
||||
import { ApiResponse } from "api/ApiResponses";
|
||||
import {
|
||||
|
|
@ -37,7 +32,6 @@ import {
|
|||
invitedUserSignupSuccess,
|
||||
fetchFeatureFlagsSuccess,
|
||||
fetchFeatureFlagsError,
|
||||
fetchFeatureFlagsInit,
|
||||
} from "actions/userActions";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { INVITE_USERS_TO_ORG_FORM } from "constants/forms";
|
||||
|
|
@ -125,11 +119,6 @@ export function* getCurrentUserSaga() {
|
|||
response.data.username !== ANONYMOUS_USERNAME
|
||||
) {
|
||||
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({
|
||||
type: ReduxActionTypes.FETCH_USER_DETAILS_SUCCESS,
|
||||
|
|
@ -137,12 +126,6 @@ export function* getCurrentUserSaga() {
|
|||
});
|
||||
if (response.data.emptyInstance) {
|
||||
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(
|
||||
PerformanceTransactionName.USER_ME_API,
|
||||
|
|
@ -161,7 +144,7 @@ export function* getCurrentUserSaga() {
|
|||
});
|
||||
|
||||
yield put({
|
||||
type: ReduxActionTypes.SAFE_CRASH_APPSMITH,
|
||||
type: ReduxActionTypes.SAFE_CRASH_APPSMITH_REQUEST,
|
||||
payload: {
|
||||
code: ERROR_CODES.SERVER_ERROR,
|
||||
},
|
||||
|
|
@ -441,8 +424,7 @@ function* fetchFeatureFlags() {
|
|||
const response: ApiResponse = yield call(UserApi.fetchFeatureFlags);
|
||||
const isValidResponse: boolean = yield validateResponse(response);
|
||||
if (isValidResponse) {
|
||||
(window as any).FEATURE_FLAGS = response.data;
|
||||
yield put(fetchFeatureFlagsSuccess());
|
||||
yield put(fetchFeatureFlagsSuccess(response.data));
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { createSelector } from "reselect";
|
||||
import { AppState } from "reducers";
|
||||
import { AppCollabReducerState } from "reducers/uiReducers/appCollabReducer";
|
||||
import { getCurrentUser } from "./usersSelectors";
|
||||
import getFeatureFlags from "../utils/featureFlags";
|
||||
import { getCurrentUser, selectFeatureFlags } from "./usersSelectors";
|
||||
import { User } from "entities/AppCollab/CollabInterfaces";
|
||||
import { ANONYMOUS_USERNAME } from "constants/userConstants";
|
||||
|
||||
|
|
@ -15,7 +14,10 @@ export const getRealtimeAppEditors = createSelector(
|
|||
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) =>
|
||||
state.ui.appCollab.pageEditors;
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ import { JSCollectionDataState } from "reducers/entityReducers/jsActionsReducer"
|
|||
import { JSCollection } from "entities/JSCollection";
|
||||
import { DefaultPlugin, GenerateCRUDEnabledPluginMap } from "../api/PluginApi";
|
||||
import { APP_MODE } from "entities/App";
|
||||
import getFeatureFlags from "utils/featureFlags";
|
||||
import { ExplorerFileEntity } from "pages/Editor/Explorer/helpers";
|
||||
import { ActionValidationConfigMap } from "constants/PropertyControlConstants";
|
||||
import { selectFeatureFlags } from "./usersSelectors";
|
||||
|
||||
export const getEntities = (state: AppState): AppState["entities"] =>
|
||||
state.entities;
|
||||
|
|
@ -663,8 +663,9 @@ export const selectFilesForExplorer = createSelector(
|
|||
getActionsForCurrentPage,
|
||||
getJSCollectionsForCurrentPage,
|
||||
selectDatasourceIdToNameMap,
|
||||
(actions, jsActions, datasourceIdToNameMap) => {
|
||||
const isJSEditorEnabled = getFeatureFlags().JS_EDITOR;
|
||||
selectFeatureFlags,
|
||||
(actions, jsActions, datasourceIdToNameMap, featureFlags) => {
|
||||
const { JS_EDITOR: isJSEditorEnabled } = featureFlags;
|
||||
const files = [...actions, ...(isJSEditorEnabled ? jsActions : [])].reduce(
|
||||
(acc, file) => {
|
||||
let group = "";
|
||||
|
|
|
|||
|
|
@ -11,4 +11,7 @@ export const getProppanePreference = (
|
|||
state: AppState,
|
||||
): PropertyPanePositionConfig | undefined => state.ui.users.propPanePreferences;
|
||||
export const getFeatureFlagsFetched = (state: AppState) =>
|
||||
state.ui.users.featureFlagFetched;
|
||||
state.ui.users.featureFlag.isFetched;
|
||||
|
||||
export const selectFeatureFlags = (state: AppState) =>
|
||||
state.ui.users.featureFlag.data;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import {
|
|||
isWidget,
|
||||
} from "workers/evaluationUtils";
|
||||
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
|
||||
// in the def
|
||||
let extraDefs: any = {};
|
||||
|
|
@ -27,12 +26,12 @@ let extraDefs: any = {};
|
|||
// or DATA_TREE.ACTION.ACTION.Api1
|
||||
export const dataTreeTypeDefCreator = (
|
||||
dataTree: DataTree,
|
||||
isJSEditorEnabled: boolean,
|
||||
): { def: Def; entityInfo: Map<string, DataTreeDefEntityInformation> } => {
|
||||
const def: any = {
|
||||
"!name": "DATA_TREE",
|
||||
};
|
||||
const entityMap: Map<string, DataTreeDefEntityInformation> = new Map();
|
||||
const isJSEditorEnabled = getFeatureFlags().JS_EDITOR;
|
||||
Object.entries(dataTree).forEach(([entityName, entity]) => {
|
||||
if (isWidget(entity)) {
|
||||
const widgetType = entity.type;
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
import FeatureFlag from "entities/FeatureFlag";
|
||||
|
||||
export default function getFeatureFlags(): FeatureFlag {
|
||||
return (window as any).FEATURE_FLAGS || {};
|
||||
}
|
||||
|
|
@ -121,6 +121,7 @@ public class SecurityConfig {
|
|||
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, USER_URL + "/invite/verify"),
|
||||
ServerWebExchangeMatchers.pathMatchers(HttpMethod.PUT, USER_URL + "/invite/confirm"),
|
||||
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_COLLECTION_URL + "/view"),
|
||||
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, PAGE_URL + "/**"),
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@ public class FeatureFlagServiceCEImpl implements FeatureFlagServiceCE {
|
|||
.flatMap(featureName -> Mono.just(featureName).zipWith(currentUser));
|
||||
|
||||
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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user