Feature flags env variable gating (#9481)
closes #9032 --------- Co-authored-by: Antoine Moreaux <moreaux.antoine@gmail.com>
This commit is contained in:
@ -171,6 +171,7 @@ export type ClientConfig = {
|
|||||||
api: ApiConfig;
|
api: ApiConfig;
|
||||||
authProviders: AuthProviders;
|
authProviders: AuthProviders;
|
||||||
billing: Billing;
|
billing: Billing;
|
||||||
|
canManageFeatureFlags: Scalars['Boolean'];
|
||||||
captcha: Captcha;
|
captcha: Captcha;
|
||||||
chromeExtensionId?: Maybe<Scalars['String']>;
|
chromeExtensionId?: Maybe<Scalars['String']>;
|
||||||
debugMode: Scalars['Boolean'];
|
debugMode: Scalars['Boolean'];
|
||||||
@ -2081,7 +2082,7 @@ export type UpdateBillingSubscriptionMutation = { __typename?: 'Mutation', updat
|
|||||||
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
|
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, isMultiWorkspaceEnabled: boolean, isSSOEnabled: boolean, defaultSubdomain?: string | null, frontDomain: string, debugMode: boolean, analyticsEnabled: boolean, chromeExtensionId?: string | null, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean, sso: Array<{ __typename?: 'SSOIdentityProvider', id: string, name: string, type: IdentityProviderType, status: SsoIdentityProviderStatus, issuer: string }> }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number } } };
|
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, isMultiWorkspaceEnabled: boolean, isSSOEnabled: boolean, defaultSubdomain?: string | null, frontDomain: string, debugMode: boolean, analyticsEnabled: boolean, chromeExtensionId?: string | null, canManageFeatureFlags: boolean, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean, sso: Array<{ __typename?: 'SSOIdentityProvider', id: string, name: string, type: IdentityProviderType, status: SsoIdentityProviderStatus, issuer: string }> }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number } } };
|
||||||
|
|
||||||
export type SkipSyncEmailOnboardingStepMutationVariables = Exact<{ [key: string]: never; }>;
|
export type SkipSyncEmailOnboardingStepMutationVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
@ -3514,6 +3515,7 @@ export const GetClientConfigDocument = gql`
|
|||||||
mutationMaximumAffectedRecords
|
mutationMaximumAffectedRecords
|
||||||
}
|
}
|
||||||
chromeExtensionId
|
chromeExtensionId
|
||||||
|
canManageFeatureFlags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -27,22 +27,17 @@ export const VerifyEffect = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getTokens = async () => {
|
if (isDefined(errorMessage)) {
|
||||||
if (isDefined(errorMessage)) {
|
enqueueSnackBar(errorMessage, {
|
||||||
enqueueSnackBar(errorMessage, {
|
variant: SnackBarVariant.Error,
|
||||||
variant: SnackBarVariant.Error,
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
if (!loginToken) {
|
|
||||||
navigate(AppPath.SignInUp);
|
|
||||||
} else {
|
|
||||||
setIsAppWaitingForFreshObjectMetadata(true);
|
|
||||||
await verify(loginToken);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!isLogged) {
|
if (isDefined(loginToken)) {
|
||||||
getTokens();
|
setIsAppWaitingForFreshObjectMetadata(true);
|
||||||
|
verify(loginToken);
|
||||||
|
} else if (!isLogged) {
|
||||||
|
navigate(AppPath.SignInUp);
|
||||||
}
|
}
|
||||||
// Verify only needs to run once at mount
|
// Verify only needs to run once at mount
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|||||||
@ -141,10 +141,7 @@ describe('useAuth', () => {
|
|||||||
const { result } = renderHooks();
|
const { result } = renderHooks();
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
const res = await result.current.signUpWithCredentials(email, password);
|
await result.current.signUpWithCredentials(email, password);
|
||||||
expect(res).toHaveProperty('user');
|
|
||||||
expect(res).toHaveProperty('workspaceMember');
|
|
||||||
expect(res).toHaveProperty('workspace');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mocks[2].result).toHaveBeenCalled();
|
expect(mocks[2].result).toHaveBeenCalled();
|
||||||
|
|||||||
@ -268,6 +268,8 @@ export const useAuth = () => {
|
|||||||
|
|
||||||
const handleVerify = useCallback(
|
const handleVerify = useCallback(
|
||||||
async (loginToken: string) => {
|
async (loginToken: string) => {
|
||||||
|
setIsVerifyPendingState(true);
|
||||||
|
|
||||||
const verifyResult = await verify({
|
const verifyResult = await verify({
|
||||||
variables: { loginToken },
|
variables: { loginToken },
|
||||||
});
|
});
|
||||||
@ -282,16 +284,11 @@ export const useAuth = () => {
|
|||||||
|
|
||||||
setTokenPair(verifyResult.data?.verify.tokens);
|
setTokenPair(verifyResult.data?.verify.tokens);
|
||||||
|
|
||||||
const { user, workspaceMember, workspace } = await loadCurrentUser();
|
await loadCurrentUser();
|
||||||
|
|
||||||
return {
|
setIsVerifyPendingState(false);
|
||||||
user,
|
|
||||||
workspaceMember,
|
|
||||||
workspace,
|
|
||||||
tokens: verifyResult.data?.verify.tokens,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
[verify, setTokenPair, loadCurrentUser],
|
[setIsVerifyPendingState, verify, setTokenPair, loadCurrentUser],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCrendentialsSignIn = useCallback(
|
const handleCrendentialsSignIn = useCallback(
|
||||||
@ -301,21 +298,9 @@ export const useAuth = () => {
|
|||||||
password,
|
password,
|
||||||
captchaToken,
|
captchaToken,
|
||||||
);
|
);
|
||||||
setIsVerifyPendingState(true);
|
await handleVerify(loginToken.token);
|
||||||
|
|
||||||
const { user, workspaceMember, workspace } = await handleVerify(
|
|
||||||
loginToken.token,
|
|
||||||
);
|
|
||||||
|
|
||||||
setIsVerifyPendingState(false);
|
|
||||||
|
|
||||||
return {
|
|
||||||
user,
|
|
||||||
workspaceMember,
|
|
||||||
workspace,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
[handleChallenge, handleVerify, setIsVerifyPendingState],
|
[handleChallenge, handleVerify],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSignOut = useCallback(async () => {
|
const handleSignOut = useCallback(async () => {
|
||||||
@ -360,13 +345,7 @@ export const useAuth = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { user, workspace, workspaceMember } = await handleVerify(
|
await handleVerify(signUpResult.data?.signUp.loginToken.token);
|
||||||
signUpResult.data?.signUp.loginToken.token,
|
|
||||||
);
|
|
||||||
|
|
||||||
setIsVerifyPendingState(false);
|
|
||||||
|
|
||||||
return { user, workspaceMember, workspace };
|
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
setIsVerifyPendingState,
|
setIsVerifyPendingState,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { apiConfigState } from '@/client-config/states/apiConfigState';
|
import { apiConfigState } from '@/client-config/states/apiConfigState';
|
||||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||||
import { billingState } from '@/client-config/states/billingState';
|
import { billingState } from '@/client-config/states/billingState';
|
||||||
|
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
|
||||||
import { captchaProviderState } from '@/client-config/states/captchaProviderState';
|
import { captchaProviderState } from '@/client-config/states/captchaProviderState';
|
||||||
import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionIdState';
|
import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionIdState';
|
||||||
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
|
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
|
||||||
@ -45,6 +46,10 @@ export const ClientConfigProviderEffect = () => {
|
|||||||
|
|
||||||
const setApiConfig = useSetRecoilState(apiConfigState);
|
const setApiConfig = useSetRecoilState(apiConfigState);
|
||||||
|
|
||||||
|
const setCanManageFeatureFlags = useSetRecoilState(
|
||||||
|
canManageFeatureFlagsState,
|
||||||
|
);
|
||||||
|
|
||||||
const { data, loading, error } = useGetClientConfigQuery({
|
const { data, loading, error } = useGetClientConfigQuery({
|
||||||
skip: clientConfigApiStatus.isLoaded,
|
skip: clientConfigApiStatus.isLoaded,
|
||||||
});
|
});
|
||||||
@ -107,6 +112,7 @@ export const ClientConfigProviderEffect = () => {
|
|||||||
defaultSubdomain: data?.clientConfig?.defaultSubdomain,
|
defaultSubdomain: data?.clientConfig?.defaultSubdomain,
|
||||||
frontDomain: data?.clientConfig?.frontDomain,
|
frontDomain: data?.clientConfig?.frontDomain,
|
||||||
});
|
});
|
||||||
|
setCanManageFeatureFlags(data?.clientConfig?.canManageFeatureFlags);
|
||||||
}, [
|
}, [
|
||||||
data,
|
data,
|
||||||
setIsDebugMode,
|
setIsDebugMode,
|
||||||
@ -125,6 +131,7 @@ export const ClientConfigProviderEffect = () => {
|
|||||||
setDomainConfiguration,
|
setDomainConfiguration,
|
||||||
setIsSSOEnabledState,
|
setIsSSOEnabledState,
|
||||||
setAuthProviders,
|
setAuthProviders,
|
||||||
|
setCanManageFeatureFlags,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
|
|||||||
@ -44,6 +44,7 @@ export const GET_CLIENT_CONFIG = gql`
|
|||||||
mutationMaximumAffectedRecords
|
mutationMaximumAffectedRecords
|
||||||
}
|
}
|
||||||
chromeExtensionId
|
chromeExtensionId
|
||||||
|
canManageFeatureFlags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { createState } from 'twenty-ui';
|
||||||
|
|
||||||
|
export const canManageFeatureFlagsState = createState<boolean>({
|
||||||
|
key: 'canManageFeatureFlagsState',
|
||||||
|
defaultValue: false,
|
||||||
|
});
|
||||||
@ -1,5 +1,7 @@
|
|||||||
|
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
|
||||||
import { SETTINGS_ADMIN_FEATURE_FLAGS_TAB_ID } from '@/settings/admin-panel/constants/SettingsAdminFeatureFlagsTabs';
|
import { SETTINGS_ADMIN_FEATURE_FLAGS_TAB_ID } from '@/settings/admin-panel/constants/SettingsAdminFeatureFlagsTabs';
|
||||||
import { useFeatureFlagsManagement } from '@/settings/admin-panel/hooks/useFeatureFlagsManagement';
|
import { useFeatureFlagsManagement } from '@/settings/admin-panel/hooks/useFeatureFlagsManagement';
|
||||||
|
import { useImpersonate } from '@/settings/admin-panel/hooks/useImpersonate';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
import { TabList } from '@/ui/layout/tab/components/TabList';
|
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||||
@ -11,6 +13,7 @@ import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/consta
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
import { getImageAbsoluteURI } from 'twenty-shared';
|
import { getImageAbsoluteURI } from 'twenty-shared';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -24,7 +27,6 @@ import {
|
|||||||
Toggle,
|
Toggle,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
import { useImpersonate } from '@/settings/admin-panel/hooks/useImpersonate';
|
|
||||||
|
|
||||||
const StyledLinkContainer = styled.div`
|
const StyledLinkContainer = styled.div`
|
||||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||||
@ -47,7 +49,7 @@ const StyledUserInfo = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTable = styled(Table)`
|
const StyledTable = styled(Table)`
|
||||||
margin-top: ${({ theme }) => theme.spacing(0.5)};
|
margin-top: ${({ theme }) => theme.spacing(3)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTabListContainer = styled.div`
|
const StyledTabListContainer = styled.div`
|
||||||
@ -87,6 +89,8 @@ export const SettingsAdminContent = () => {
|
|||||||
error,
|
error,
|
||||||
} = useFeatureFlagsManagement();
|
} = useFeatureFlagsManagement();
|
||||||
|
|
||||||
|
const canManageFeatureFlags = useRecoilValue(canManageFeatureFlagsState);
|
||||||
|
|
||||||
const handleSearch = async () => {
|
const handleSearch = async () => {
|
||||||
setActiveTabId('');
|
setActiveTabId('');
|
||||||
|
|
||||||
@ -151,37 +155,39 @@ export const SettingsAdminContent = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<StyledTable>
|
{canManageFeatureFlags && (
|
||||||
<TableRow
|
<StyledTable>
|
||||||
gridAutoColumns="1fr 100px"
|
|
||||||
mobileGridAutoColumns="1fr 80px"
|
|
||||||
>
|
|
||||||
<TableHeader>Feature Flag</TableHeader>
|
|
||||||
<TableHeader align="right">Status</TableHeader>
|
|
||||||
</TableRow>
|
|
||||||
|
|
||||||
{activeWorkspace.featureFlags.map((flag) => (
|
|
||||||
<TableRow
|
<TableRow
|
||||||
gridAutoColumns="1fr 100px"
|
gridAutoColumns="1fr 100px"
|
||||||
mobileGridAutoColumns="1fr 80px"
|
mobileGridAutoColumns="1fr 80px"
|
||||||
key={flag.key}
|
|
||||||
>
|
>
|
||||||
<TableCell>{flag.key}</TableCell>
|
<TableHeader>Feature Flag</TableHeader>
|
||||||
<TableCell align="right">
|
<TableHeader align="right">Status</TableHeader>
|
||||||
<Toggle
|
|
||||||
value={flag.value}
|
|
||||||
onChange={(newValue) =>
|
|
||||||
handleFeatureFlagUpdate(
|
|
||||||
activeWorkspace.id,
|
|
||||||
flag.key,
|
|
||||||
newValue,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
|
||||||
</StyledTable>
|
{activeWorkspace.featureFlags.map((flag) => (
|
||||||
|
<TableRow
|
||||||
|
gridAutoColumns="1fr 100px"
|
||||||
|
mobileGridAutoColumns="1fr 80px"
|
||||||
|
key={flag.key}
|
||||||
|
>
|
||||||
|
<TableCell>{flag.key}</TableCell>
|
||||||
|
<TableCell align="right">
|
||||||
|
<Toggle
|
||||||
|
value={flag.value}
|
||||||
|
onChange={(newValue) =>
|
||||||
|
handleFeatureFlagUpdate(
|
||||||
|
activeWorkspace.id,
|
||||||
|
flag.key,
|
||||||
|
newValue,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</StyledTable>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -190,8 +196,16 @@ export const SettingsAdminContent = () => {
|
|||||||
<>
|
<>
|
||||||
<Section>
|
<Section>
|
||||||
<H2Title
|
<H2Title
|
||||||
title="Feature Flags & Impersonation"
|
title={
|
||||||
description="Look up users and manage their workspace feature flags or impersonate it."
|
canManageFeatureFlags
|
||||||
|
? 'Feature Flags & Impersonation'
|
||||||
|
: 'User Impersonation'
|
||||||
|
}
|
||||||
|
description={
|
||||||
|
canManageFeatureFlags
|
||||||
|
? 'Look up users and manage their workspace feature flags or impersonate them.'
|
||||||
|
: 'Look up users to impersonate them.'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
|
|||||||
@ -1,15 +1,24 @@
|
|||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import { useImpersonateMutation } from '~/generated/graphql';
|
import { useImpersonateMutation } from '~/generated/graphql';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||||
|
import { useAuth } from '@/auth/hooks/useAuth';
|
||||||
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
|
import { isAppWaitingForFreshObjectMetadataState } from '@/object-metadata/states/isAppWaitingForFreshObjectMetadataState';
|
||||||
|
|
||||||
export const useImpersonate = () => {
|
export const useImpersonate = () => {
|
||||||
const [currentUser] = useRecoilState(currentUserState);
|
const [currentUser] = useRecoilState(currentUserState);
|
||||||
const [impersonate] = useImpersonateMutation();
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
|
const setIsAppWaitingForFreshObjectMetadata = useSetRecoilState(
|
||||||
|
isAppWaitingForFreshObjectMetadataState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { verify } = useAuth();
|
||||||
|
|
||||||
|
const [impersonate] = useImpersonateMutation();
|
||||||
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@ -39,6 +48,13 @@ export const useImpersonate = () => {
|
|||||||
|
|
||||||
const { loginToken, workspace } = impersonateResult.data.impersonate;
|
const { loginToken, workspace } = impersonateResult.data.impersonate;
|
||||||
|
|
||||||
|
if (workspace.id === currentWorkspace?.id) {
|
||||||
|
setIsAppWaitingForFreshObjectMetadata(true);
|
||||||
|
await verify(loginToken.token);
|
||||||
|
setIsAppWaitingForFreshObjectMetadata(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return redirectToWorkspaceDomain(workspace.subdomain, AppPath.Verify, {
|
return redirectToWorkspaceDomain(workspace.subdomain, AppPath.Verify, {
|
||||||
loginToken: loginToken.token,
|
loginToken: loginToken.token,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -40,4 +40,5 @@ export const mockedClientConfig: ClientConfig = {
|
|||||||
__typename: 'Captcha',
|
__typename: 'Captcha',
|
||||||
},
|
},
|
||||||
api: { mutationMaximumAffectedRecords: 100 },
|
api: { mutationMaximumAffectedRecords: 100 },
|
||||||
|
canManageFeatureFlags: true,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,15 +3,15 @@ import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
|||||||
|
|
||||||
import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
|
import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
|
||||||
import { ImpersonateInput } from 'src/engine/core-modules/admin-panel/dtos/impersonate.input';
|
import { ImpersonateInput } from 'src/engine/core-modules/admin-panel/dtos/impersonate.input';
|
||||||
|
import { ImpersonateOutput } from 'src/engine/core-modules/admin-panel/dtos/impersonate.output';
|
||||||
import { UpdateWorkspaceFeatureFlagInput } from 'src/engine/core-modules/admin-panel/dtos/update-workspace-feature-flag.input';
|
import { UpdateWorkspaceFeatureFlagInput } from 'src/engine/core-modules/admin-panel/dtos/update-workspace-feature-flag.input';
|
||||||
import { UserLookup } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.entity';
|
import { UserLookup } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.entity';
|
||||||
import { UserLookupInput } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.input';
|
import { UserLookupInput } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.input';
|
||||||
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
|
import { ImpersonateGuard } from 'src/engine/guards/impersonate-guard';
|
||||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
import { ImpersonateGuard } from 'src/engine/guards/impersonate-guard';
|
|
||||||
import { ImpersonateOutput } from 'src/engine/core-modules/admin-panel/dtos/impersonate.output';
|
|
||||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@UseFilters(AuthGraphqlApiExceptionFilter)
|
@UseFilters(AuthGraphqlApiExceptionFilter)
|
||||||
|
|||||||
@ -8,14 +8,14 @@ import {
|
|||||||
AuthException,
|
AuthException,
|
||||||
AuthExceptionCode,
|
AuthExceptionCode,
|
||||||
} from 'src/engine/core-modules/auth/auth.exception';
|
} from 'src/engine/core-modules/auth/auth.exception';
|
||||||
|
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|
||||||
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
|
||||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
|
||||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
|
||||||
import { featureFlagValidator } from 'src/engine/core-modules/feature-flag/validates/feature-flag.validate';
|
import { featureFlagValidator } from 'src/engine/core-modules/feature-flag/validates/feature-flag.validate';
|
||||||
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
|
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
||||||
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminPanelService {
|
export class AdminPanelService {
|
||||||
|
|||||||
@ -94,4 +94,7 @@ export class ClientConfig {
|
|||||||
|
|
||||||
@Field(() => ApiConfig)
|
@Field(() => ApiConfig)
|
||||||
api: ApiConfig;
|
api: ApiConfig;
|
||||||
|
|
||||||
|
@Field(() => Boolean)
|
||||||
|
canManageFeatureFlags: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,6 +59,9 @@ export class ClientConfigResolver {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
analyticsEnabled: this.environmentService.get('ANALYTICS_ENABLED'),
|
analyticsEnabled: this.environmentService.get('ANALYTICS_ENABLED'),
|
||||||
|
canManageFeatureFlags:
|
||||||
|
this.environmentService.get('DEBUG_MODE') ||
|
||||||
|
this.environmentService.get('IS_BILLING_ENABLED'),
|
||||||
};
|
};
|
||||||
|
|
||||||
return Promise.resolve(clientConfig);
|
return Promise.resolve(clientConfig);
|
||||||
|
|||||||
Reference in New Issue
Block a user