From cb0398ca6371958983ca101ba9d21b4205c5af22 Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Wed, 14 Dec 2022 10:00:14 +0100 Subject: [PATCH] loosen permissions for change requests (#2682) --- .../ChangeRequestOverview.tsx | 1 + .../ChangeRequestPermissions.test.tsx | 242 ++++++++++++++++++ .../PermissionButton/PermissionButton.tsx | 184 +++++++------ .../PermissionIconButton.tsx | 63 ++++- .../PermissionSwitch/PermissionSwitch.tsx | 71 ++++- .../FeatureStrategyCreate.tsx | 1 + .../FeatureStrategyEdit.tsx | 1 + .../FeatureStrategyForm.tsx | 18 +- .../CopyStrategyIconMenu.tsx | 15 +- frontend/src/hooks/useHasAccess.ts | 96 +++++++ frontend/src/utils/testIds.ts | 1 + 11 files changed, 572 insertions(+), 121 deletions(-) create mode 100644 frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx create mode 100644 frontend/src/hooks/useHasAccess.ts diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx index 9e2fc5571a..5c272bcdb0 100644 --- a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx +++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx @@ -170,6 +170,7 @@ export const ChangeRequestOverview: FC = () => { > {changeRequest.approvals?.map(approver => ( + testServerRoute( + server, + '/api/admin/projects/default/change-requests/config', + [ + { + environment: 'development', + type: 'development', + requiredApprovals: null, + changeRequestEnabled: env === 'development', + }, + { + environment: 'production', + type: 'production', + requiredApprovals: 1, + changeRequestEnabled: env === 'production', + }, + { + environment: 'custom', + type: 'production', + requiredApprovals: null, + changeRequestEnabled: env === 'custom', + }, + ] + ); + +const uiConfigForEnterprise = () => + testServerRoute(server, '/api/admin/ui-config', { + environment: 'Open Source', + flags: { + changeRequests: true, + }, + versionInfo: { + current: { oss: '4.18.0-beta.5', enterprise: '4.17.0-beta.1' }, + }, + disablePasswordAuth: false, + }); + +const setupOtherRoutes = (feature: string) => { + testServerRoute( + server, + 'api/admin/projects/default/change-requests/pending', + [] + ); + testServerRoute(server, '/api/admin/projects/default', {}); + testServerRoute(server, `api/admin/client-metrics/features/${feature}`, { + version: 1, + maturity: 'stable', + featureName: feature, + lastHourUsage: [], + seenApplications: [], + }); + testServerRoute(server, `api/admin/features/${feature}/tags`, { + version: 1, + tags: [], + }); + testServerRoute(server, `api/admin/strategies`, { + version: 1, + strategies: [ + { + displayName: 'Standard', + name: 'default', + editable: false, + description: + 'The standard strategy is strictly on / off for your entire userbase.', + parameters: [], + deprecated: false, + }, + { + displayName: 'UserIDs', + name: 'userWithId', + editable: false, + description: + 'Enable the feature for a specific set of userIds.', + parameters: [ + { + name: 'userIds', + type: 'list', + description: '', + required: false, + }, + ], + deprecated: false, + }, + ], + }); +}; + +const userHasPermissions = (permissions: Array) => { + testServerRoute(server, 'api/admin/user', { + user: { + isAPI: false, + id: 2, + name: 'Test', + email: 'test@getunleash.ai', + imageUrl: + 'https://gravatar.com/avatar/e55646b526ff342ff8b43721f0cbdd8e?size=42&default=retro', + seenAt: '2022-11-29T08:21:52.581Z', + loginAttempts: 0, + createdAt: '2022-11-21T10:10:33.074Z', + }, + permissions, + feedback: [], + splash: {}, + }); +}; + +const featureEnvironments = ( + feature: string, + environments: Array<{ name: string; strategies: Array }> +) => { + testServerRoute(server, `/api/admin/projects/default/features/${feature}`, { + environments: environments.map(env => ({ + name: env.name, + enabled: false, + type: 'production', + sortOrder: 1, + strategies: env.strategies.map(strategy => ({ + name: strategy, + id: Math.random(), + constraints: [], + parameters: [], + sortOrder: 1, + })), + })), + name: feature, + impressionData: false, + description: '', + project: 'default', + stale: false, + variants: [], + createdAt: '2022-11-14T08:16:33.338Z', + lastSeenAt: null, + type: 'release', + archived: false, + }); +}; + +const UnleashUiSetup: FC<{ path: string; pathTemplate: string }> = ({ + children, + path, + pathTemplate, +}) => ( + + + + + + + + + + + + + +); + +const strategiesAreDisplayed = async ( + firstStrategy: string, + secondStrategy: string +) => { + await screen.findByText(firstStrategy); + await screen.findByText(secondStrategy); +}; + +const deleteButtonsActiveInChangeRequestEnv = async () => { + const deleteButtons = screen.getAllByTestId('STRATEGY_FORM_REMOVE_ID'); + expect(deleteButtons.length).toBe(2); + + // wait for change request config to be loaded + await waitFor(() => { + // production + const productionStrategyDeleteButton = deleteButtons[0]; + expect(productionStrategyDeleteButton).not.toBeDisabled(); + + // custom env + const customEnvStrategyDeleteButton = deleteButtons[1]; + expect(customEnvStrategyDeleteButton).toBeDisabled(); + }); +}; + +const copyButtonsActiveInOtherEnv = async () => { + const copyButtons = screen.getAllByTestId('STRATEGY_FORM_COPY_ID'); + expect(copyButtons.length).toBe(2); + + // production + const productionStrategyCopyButton = copyButtons[0]; + expect(productionStrategyCopyButton).toBeDisabled(); + + // custom env + const customEnvStrategyCopyButton = copyButtons[1]; + expect(customEnvStrategyCopyButton).not.toBeDisabled(); +}; + +test('user without priviledges can only perform change requests actions', async () => { + const project = 'default'; + const featureName = 'test'; + featureEnvironments(featureName, [ + { name: 'development', strategies: [] }, + { name: 'production', strategies: ['userWithId'] }, + { name: 'custom', strategies: ['default'] }, + ]); + userHasPermissions([ + { + project, + environment: 'production', + permission: 'APPLY_CHANGE_REQUEST', + }, + ]); + changeRequestsEnabledIn('production'); + uiConfigForEnterprise(); + setupOtherRoutes(featureName); + + render( + + + + ); + + await strategiesAreDisplayed('UserIDs', 'Standard'); + await deleteButtonsActiveInChangeRequestEnv(); + await copyButtonsActiveInOtherEnv(); +}); diff --git a/frontend/src/component/common/PermissionButton/PermissionButton.tsx b/frontend/src/component/common/PermissionButton/PermissionButton.tsx index b011247cad..1a2a2454aa 100644 --- a/frontend/src/component/common/PermissionButton/PermissionButton.tsx +++ b/frontend/src/component/common/PermissionButton/PermissionButton.tsx @@ -1,7 +1,6 @@ import { Button, ButtonProps } from '@mui/material'; import { Lock } from '@mui/icons-material'; -import AccessContext from 'contexts/AccessContext'; -import React, { useContext } from 'react'; +import React from 'react'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { TooltipResolver, @@ -9,6 +8,10 @@ import { } from 'component/common/TooltipResolver/TooltipResolver'; import { formatAccessText } from 'utils/formatAccessText'; import { useId } from 'hooks/useId'; +import { + useHasRootAccess, + useHasProjectEnvironmentAccess, +} from 'hooks/useHasAccess'; export interface IPermissionButtonProps extends Omit { permission: string | string[]; @@ -19,85 +22,110 @@ export interface IPermissionButtonProps extends Omit { tooltipProps?: Omit; } -const PermissionButton: React.FC = React.forwardRef( - ( - { - permission, - variant = 'contained', - color = 'primary', - onClick, - children, - disabled, - projectId, - environmentId, - tooltipProps, - ...rest - }, - ref - ) => { - const { hasAccess } = useContext(AccessContext); - const id = useId(); - let access; +interface IPermissionBaseButtonProps extends IPermissionButtonProps { + access: boolean; +} - const handleAccess = () => { - let access; - if (Array.isArray(permission)) { - access = permission.some(permission => { - if (projectId && environmentId) { - return hasAccess(permission, projectId, environmentId); - } else if (projectId) { - return hasAccess(permission, projectId); - } else { - return hasAccess(permission); - } - }); - } else { - if (projectId && environmentId) { - access = hasAccess(permission, projectId, environmentId); - } else if (projectId) { - access = hasAccess(permission, projectId); - } else { - access = hasAccess(permission); - } - } +export interface IProjectPermissionButtonProps extends IPermissionButtonProps { + projectId: string; + environmentId: string; +} - return access; - }; - - access = handleAccess(); - - return ( - - - - - +const ProjectEnvironmentPermissionButton: React.FC = + React.forwardRef((props, ref) => { + const access = useHasProjectEnvironmentAccess( + props.permission, + props.environmentId, + props.projectId ); + + return ; + }); + +const RootPermissionButton: React.FC = React.forwardRef( + (props, ref) => { + const access = useHasRootAccess( + props.permission, + props.environmentId, + props.projectId + ); + + return ; + } +); + +const BasePermissionButton: React.FC = + React.forwardRef( + ( + { + permission, + access, + variant = 'contained', + color = 'primary', + onClick, + children, + disabled, + projectId, + environmentId, + tooltipProps, + ...rest + }, + ref + ) => { + const id = useId(); + + return ( + + + + + + ); + } + ); + +const PermissionButton: React.FC = React.forwardRef( + (props, ref) => { + if ( + typeof props.projectId !== 'undefined' && + typeof props.environmentId !== 'undefined' + ) { + return ( + + ); + } + return ; } ); diff --git a/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx b/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx index bd8c4fcdb5..fc1b4dd882 100644 --- a/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx +++ b/frontend/src/component/common/PermissionIconButton/PermissionIconButton.tsx @@ -1,6 +1,5 @@ import { IconButton, IconButtonProps } from '@mui/material'; -import React, { ReactNode, useContext } from 'react'; -import AccessContext from 'contexts/AccessContext'; +import React, { ReactNode } from 'react'; import { Link } from 'react-router-dom'; import { ITooltipResolverProps, @@ -8,6 +7,10 @@ import { } from 'component/common/TooltipResolver/TooltipResolver'; import { formatAccessText } from 'utils/formatAccessText'; import { useId } from 'hooks/useId'; +import { + useHasProjectEnvironmentAccess, + useHasRootAccess, +} from 'hooks/useHasAccess'; interface IPermissionIconButtonProps { permission: string; @@ -33,7 +36,33 @@ interface ILinkProps extends IPermissionIconButtonProps { to: string; } -const PermissionIconButton = ({ +const RootPermissionIconButton = (props: IButtonProps | ILinkProps) => { + const access = useHasRootAccess( + props.permission, + props.environmentId, + props.projectId + ); + + return ; +}; + +const ProjectEnvironmentPermissionIconButton = ( + props: (IButtonProps | ILinkProps) & { + environmentId: string; + projectId: string; + } +) => { + const access = useHasProjectEnvironmentAccess( + props.permission, + props.environmentId, + props.projectId + ); + + return ; +}; + +const BasePermissionIconButton = ({ + access, permission, projectId, children, @@ -41,18 +70,8 @@ const PermissionIconButton = ({ tooltipProps, disabled, ...rest -}: IButtonProps | ILinkProps) => { - const { hasAccess } = useContext(AccessContext); +}: (IButtonProps | ILinkProps) & { access: boolean }) => { const id = useId(); - let access; - - if (projectId && environmentId) { - access = hasAccess(permission, projectId, environmentId); - } else if (projectId) { - access = hasAccess(permission, projectId); - } else { - access = hasAccess(permission); - } return ( { + if ( + typeof props.projectId !== 'undefined' && + typeof props.environmentId !== 'undefined' + ) { + return ( + + ); + } + return ; +}; + export default PermissionIconButton; diff --git a/frontend/src/component/common/PermissionSwitch/PermissionSwitch.tsx b/frontend/src/component/common/PermissionSwitch/PermissionSwitch.tsx index d0c91e65ee..4829fa15ce 100644 --- a/frontend/src/component/common/PermissionSwitch/PermissionSwitch.tsx +++ b/frontend/src/component/common/PermissionSwitch/PermissionSwitch.tsx @@ -1,8 +1,11 @@ import { Switch, SwitchProps } from '@mui/material'; -import AccessContext from 'contexts/AccessContext'; -import React, { useContext } from 'react'; +import React from 'react'; import { formatAccessText } from 'utils/formatAccessText'; import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver'; +import { + useHasProjectEnvironmentAccess, + useHasRootAccess, +} from 'hooks/useHasAccess'; interface IPermissionSwitchProps extends SwitchProps { permission: string; @@ -14,11 +17,42 @@ interface IPermissionSwitchProps extends SwitchProps { checked: boolean; } -const PermissionSwitch = React.forwardRef< +interface IBasePermissionSwitchProps extends IPermissionSwitchProps { + access: boolean; +} + +const ProjectenvironmentPermissionSwitch = React.forwardRef< + HTMLButtonElement, + IPermissionSwitchProps & { projectId: string; environmentId: string } +>((props, ref) => { + const access = useHasProjectEnvironmentAccess( + props.permission, + props.environmentId, + props.projectId + ); + + return ; +}); + +const RootPermissionSwitch = React.forwardRef< HTMLButtonElement, IPermissionSwitchProps +>((props, ref) => { + const access = useHasRootAccess( + props.permission, + props.environmentId, + props.projectId + ); + + return ; +}); + +const BasePermissionSwitch = React.forwardRef< + HTMLButtonElement, + IBasePermissionSwitchProps >((props, ref) => { const { + access, permission, tooltip, disabled, @@ -29,17 +63,6 @@ const PermissionSwitch = React.forwardRef< ...rest } = props; - const { hasAccess } = useContext(AccessContext); - - let access; - if (projectId && environmentId) { - access = hasAccess(permission, projectId, environmentId); - } else if (projectId) { - access = hasAccess(permission, projectId); - } else { - access = hasAccess(permission); - } - return ( @@ -56,4 +79,24 @@ const PermissionSwitch = React.forwardRef< ); }); +const PermissionSwitch = React.forwardRef< + HTMLButtonElement, + IPermissionSwitchProps +>((props, ref) => { + if ( + typeof props.projectId !== 'undefined' && + typeof props.environmentId !== 'undefined' + ) { + return ( + + ); + } + return ; +}); + export default PermissionSwitch; diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate.tsx index 5a990294a2..0047c06518 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate.tsx @@ -150,6 +150,7 @@ export const FeatureStrategyCreate = () => { } > { } > void; @@ -48,6 +49,7 @@ interface IFeatureStrategyFormProps { } export const FeatureStrategyForm = ({ + projectId, feature, environmentId, permission, @@ -64,7 +66,11 @@ export const FeatureStrategyForm = ({ const [showProdGuard, setShowProdGuard] = useState(false); const hasValidConstraints = useConstraintsValidation(strategy.constraints); const enableProdGuard = useFeatureStrategyProdGuard(feature, environmentId); - const { hasAccess } = useContext(AccessContext); + const access = useHasProjectEnvironmentAccess( + permission, + environmentId, + projectId + ); const { strategyDefinition } = useStrategy(strategy?.name); const { data } = usePendingChangeRequests(feature.project); @@ -205,11 +211,7 @@ export const FeatureStrategyForm = ({ setStrategy={setStrategy} validateParameter={validateParameter} errors={errors} - hasAccess={hasAccess( - permission, - feature.project, - environmentId - )} + hasAccess={access} />
diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/CopyStrategyIconMenu/CopyStrategyIconMenu.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/CopyStrategyIconMenu/CopyStrategyIconMenu.tsx index 3512bb62d8..ab7f9a82c9 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/CopyStrategyIconMenu/CopyStrategyIconMenu.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/CopyStrategyIconMenu/CopyStrategyIconMenu.tsx @@ -11,7 +11,6 @@ import { AddToPhotos as CopyIcon, Lock } from '@mui/icons-material'; import { IFeatureStrategyPayload } from 'interfaces/strategy'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { IFeatureEnvironment } from 'interfaces/featureToggle'; -import AccessContext from 'contexts/AccessContext'; import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; @@ -20,9 +19,11 @@ import useToast from 'hooks/useToast'; import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmutable'; import { formatUnknownError } from 'utils/formatUnknownError'; import { useChangeRequestAddStrategy } from 'hooks/useChangeRequestAddStrategy'; -import { ChangeRequestDialogue } from '../../../../../../../../../changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog'; -import { CopyStrategyMessage } from '../../../../../../../../../changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/CopyStrategyMessage'; +import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog'; +import { CopyStrategyMessage } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/CopyStrategyMessage'; import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; +import { useCheckProjectAccess } from 'hooks/useHasAccess'; +import { STRATEGY_FORM_COPY_ID } from 'utils/testIds'; interface ICopyStrategyIconMenuProps { environmentId: string; @@ -50,7 +51,7 @@ export const CopyStrategyIconMenu: VFC = ({ const onClose = () => { setAnchorEl(null); }; - const { hasAccess } = useContext(AccessContext); + const checkAccess = useCheckProjectAccess(projectId); const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); const { @@ -98,7 +99,7 @@ export const CopyStrategyIconMenu: VFC = ({ }; const enabled = environments.some(environment => - hasAccess(CREATE_FEATURE_STRATEGY, projectId, environment) + checkAccess(CREATE_FEATURE_STRATEGY, environment) ); return ( @@ -132,6 +133,7 @@ export const CopyStrategyIconMenu: VFC = ({ onClick={(event: MouseEvent) => { setAnchorEl(event.currentTarget); }} + data-testid={STRATEGY_FORM_COPY_ID} disabled={!enabled} > @@ -148,9 +150,8 @@ export const CopyStrategyIconMenu: VFC = ({ }} > {environments.map(environment => { - const access = hasAccess( + const access = checkAccess( CREATE_FEATURE_STRATEGY, - projectId, environment ); diff --git a/frontend/src/hooks/useHasAccess.ts b/frontend/src/hooks/useHasAccess.ts new file mode 100644 index 0000000000..d7c56b5f6e --- /dev/null +++ b/frontend/src/hooks/useHasAccess.ts @@ -0,0 +1,96 @@ +import { useContext } from 'react'; +import AccessContext from '../contexts/AccessContext'; +import { useChangeRequestsEnabled } from './useChangeRequestsEnabled'; +import { + CREATE_FEATURE_STRATEGY, + UPDATE_FEATURE_STRATEGY, + DELETE_FEATURE_STRATEGY, + UPDATE_FEATURE_ENVIRONMENT, +} from '../component/providers/AccessProvider/permissions'; + +const useCheckProjectPermissions = (projectId?: string) => { + const { hasAccess } = useContext(AccessContext); + + const checkPermission = ( + permission: string, + projectId?: string, + environmentId?: string + ) => { + if (projectId && environmentId) { + return hasAccess(permission, projectId, environmentId); + } else if (projectId) { + return hasAccess(permission, projectId); + } else { + return hasAccess(permission); + } + }; + + const checkPermissions = ( + permissions: string | string[], + projectId?: string, + environmentId?: string + ) => { + if (Array.isArray(permissions)) { + return permissions.some(permission => + checkPermission(permission, projectId, environmentId) + ); + } else { + return checkPermission(permissions, projectId, environmentId); + } + }; + + return (permissions: string | string[], environmentId?: string) => { + return checkPermissions(permissions, projectId, environmentId); + }; +}; + +export const useCheckProjectAccess = (projectId: string) => { + const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); + const checkAccess = useCheckProjectPermissions(projectId); + + return (permission: string, environment: string) => { + return ( + isChangeRequestConfigured(environment) || + checkAccess(permission, environment) + ); + }; +}; + +const ALLOWED_CHANGE_REQUEST_PERMISSIONS = [ + CREATE_FEATURE_STRATEGY, + UPDATE_FEATURE_STRATEGY, + DELETE_FEATURE_STRATEGY, + UPDATE_FEATURE_ENVIRONMENT, +]; + +const intersect = (array1: string[], array2: string[]) => { + return array1.filter(value => array2.includes(value)).length > 0; +}; + +export const useHasProjectEnvironmentAccess = ( + permission: string | string[], + environmentId: string, + projectId: string +) => { + const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); + const checkAccess = useCheckProjectPermissions(projectId); + const changeRequestMode = isChangeRequestConfigured(environmentId); + const emptyArray: string[] = []; + + return ( + (changeRequestMode && + intersect( + ALLOWED_CHANGE_REQUEST_PERMISSIONS, + emptyArray.concat(permission) + )) || + checkAccess(permission, environmentId) + ); +}; + +export const useHasRootAccess = ( + permissions: string | string[], + environmentId?: string, + projectId?: string +) => { + return useCheckProjectPermissions(projectId)(permissions, environmentId); +}; diff --git a/frontend/src/utils/testIds.ts b/frontend/src/utils/testIds.ts index c443f62c5f..39bfd2467d 100644 --- a/frontend/src/utils/testIds.ts +++ b/frontend/src/utils/testIds.ts @@ -61,6 +61,7 @@ export const STRATEGY_INPUT_LIST = 'STRATEGY_INPUT_LIST'; export const ADD_TO_STRATEGY_INPUT_LIST = 'ADD_TO_STRATEGY_INPUT_LIST'; export const STRATEGY_FORM_SUBMIT_ID = 'STRATEGY_FORM_SUBMIT_ID'; export const STRATEGY_FORM_REMOVE_ID = 'STRATEGY_FORM_REMOVE_ID'; +export const STRATEGY_FORM_COPY_ID = 'STRATEGY_FORM_COPY_ID'; /* SPLASH */ export const CLOSE_SPLASH = 'CLOSE_SPLASH';