1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-09 00:18:00 +01:00

Merge branch 'main' into fix-rollout-datatype

This commit is contained in:
sighphyre 2022-03-23 11:39:26 +02:00 committed by GitHub
commit a724127970
23 changed files with 434 additions and 218 deletions

View File

@ -51,8 +51,8 @@
"@types/jest": "27.4.1",
"@types/lodash.clonedeep": "4.5.6",
"@types/node": "17.0.18",
"@types/react": "17.0.40",
"@types/react-dom": "17.0.13",
"@types/react": "17.0.41",
"@types/react-dom": "17.0.14",
"@types/react-router-dom": "5.3.3",
"@types/react-test-renderer": "17.0.1",
"@types/react-timeago": "4.1.3",

View File

@ -1,5 +1,4 @@
import ConditionallyRender from './common/ConditionallyRender';
import EnvironmentSplash from './common/EnvironmentSplash/EnvironmentSplash';
import Feedback from './common/Feedback/Feedback';
import LayoutPicker from './layout/LayoutPicker/LayoutPicker';
import Loader from './common/Loader/Loader';
@ -10,28 +9,15 @@ import ToastRenderer from './common/ToastRenderer/ToastRenderer';
import styles from './styles.module.scss';
import { Redirect, Route, Switch } from 'react-router-dom';
import { routes } from './menu/routes';
import { useAuthDetails } from '../hooks/api/getters/useAuth/useAuthDetails';
import { useAuthUser } from '../hooks/api/getters/useAuth/useAuthUser';
import { useAuthSplash } from '../hooks/api/getters/useAuth/useAuthSplash';
import { useAuthDetails } from 'hooks/api/getters/useAuth/useAuthDetails';
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
import { SplashPageRedirect } from 'component/splash/SplashPageRedirect/SplashPageRedirect';
export const App = () => {
const { splash, refetchSplash } = useAuthSplash();
const { authDetails } = useAuthDetails();
const { user } = useAuthUser();
const isLoggedIn = Boolean(user?.id);
const hasFetchedAuth = Boolean(authDetails || user);
const showEnvSplash = isLoggedIn && splash?.environment === false;
const renderMainLayoutRoutes = () => {
return routes.filter(route => route.layout === 'main').map(renderRoute);
};
const renderStandaloneRoutes = () => {
return routes
.filter(route => route.layout === 'standalone')
.map(renderRoute);
};
const isUnauthorized = (): boolean => {
return !isLoggedIn;
@ -73,34 +59,22 @@ export const App = () => {
elseShow={
<div className={styles.container}>
<ToastRenderer />
<ConditionallyRender
condition={showEnvSplash}
show={
<EnvironmentSplash onFinish={refetchSplash} />
}
elseShow={
<LayoutPicker>
<Switch>
<ProtectedRoute
exact
path="/"
unauthorized={isUnauthorized()}
component={Redirect}
renderProps={{ to: '/features' }}
/>
{renderMainLayoutRoutes()}
{renderStandaloneRoutes()}
<Route
path="/404"
component={NotFound}
/>
<Redirect to="/404" />
</Switch>
<Feedback openUrl="http://feedback.unleash.run" />
</LayoutPicker>
}
/>
<LayoutPicker>
<Switch>
<ProtectedRoute
exact
path="/"
unauthorized={isUnauthorized()}
component={Redirect}
renderProps={{ to: '/features' }}
/>
{routes.map(renderRoute)}
<Route path="/404" component={NotFound} />
<Redirect to="/404" />
</Switch>
<Feedback openUrl="http://feedback.unleash.run" />
<SplashPageRedirect />
</LayoutPicker>
</div>
}
/>

View File

@ -5,10 +5,10 @@ import { CANCEL } from '../ConstraintAccordionEdit';
import { ConstraintFormHeader } from './ConstraintFormHeader/ConstraintFormHeader';
import { useStyles } from './ConstraintAccordionEditBody.styles';
import React from 'react';
import { Alert } from '@material-ui/lab';
import { newOperators } from 'constants/operators';
import ConditionallyRender from 'component/common/ConditionallyRender/ConditionallyRender';
import { oneOf } from 'utils/one-of';
import { OperatorUpgradeAlert } from 'component/common/OperatorUpgradeAlert/OperatorUpgradeAlert';
interface IConstraintAccordionBody {
localConstraint: IConstraint;
@ -37,12 +37,7 @@ export const ConstraintAccordionEditBody: React.FC<
<>
<ConditionallyRender
condition={oneOf(newOperators, localConstraint.operator)}
show={
<Alert severity="warning">
In order to use this constraint operator, you need to
update your SDK to the latest version.
</Alert>
}
show={<OperatorUpgradeAlert />}
/>
<div className={styles.inputContainer}>

View File

@ -38,7 +38,7 @@ const FormTemplate: React.FC<ICreateProps> = ({
const { setToastData } = useToast();
const styles = useStyles();
const commonStyles = useCommonStyles();
const smallScreen = useMediaQuery(`(max-width:${900}px)`);
const smallScreen = useMediaQuery(`(max-width:${899}px)`);
const copyCommand = () => {
if (copy(formatApiCode())) {

View File

@ -0,0 +1,23 @@
import { Alert } from '@material-ui/lab';
export const OperatorUpgradeAlert = () => {
return (
<Alert severity="warning">
Remember to update your Unleash client! New operators require new
SDK versions. <OperatorDocsLink />.
</Alert>
);
};
const OperatorDocsLink = () => {
return (
<a
href="https://docs.getunleash.io/advanced/strategy_constraints#strategy-constraint-operators"
target="_blank"
rel="noreferrer"
style={{ color: 'inherit' }}
>
Read more
</a>
);
};

View File

@ -17,10 +17,12 @@ const LayoutPicker = ({ children }) => {
const isResetPasswordSuccessPage = matchPath(pathname, {
path: '/reset-password-success',
});
const isForgottenPasswordPage = matchPath(pathname, {
path: '/forgotten-password',
});
const isSplashPage = matchPath(pathname, {
path: '/splash/:id',
});
const is404 = matchPath(pathname, { path: '/404' });
@ -30,6 +32,7 @@ const LayoutPicker = ({ children }) => {
isChangePasswordPage ||
isResetPasswordSuccessPage ||
isForgottenPasswordPage ||
isSplashPage ||
is404
);
};

View File

@ -4,7 +4,13 @@ exports[`returns all baseRoutes 1`] = `
Array [
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"path": "/splash/:splashId",
"title": "Unleash",
"type": "protected",
},
Object {
"component": [Function],
"menu": Object {},
"parent": "/projects",
"path": "/projects/create",
@ -13,7 +19,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/projects",
"path": "/projects/:id/edit",
@ -22,7 +27,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/archive",
"path": "/projects/:id/archived",
@ -31,7 +35,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/projects/:id/features/:name/:activeTab",
"path": "/projects/:id/features/:name/:activeTab/copy",
@ -40,7 +43,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/projects",
"path": "/projects/:projectId/features/:featureId/edit",
@ -50,7 +52,6 @@ Array [
Object {
"component": [Function],
"flags": "E",
"layout": "main",
"menu": Object {},
"parent": "/projects",
"path": "/projects/:projectId/features/:featureId",
@ -59,7 +60,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/projects",
"path": "/projects/:id/features/:name/:activeTab",
@ -68,7 +68,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/projects/:id/features",
"path": "/projects/:projectId/create-toggle",
@ -77,7 +76,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/features",
"path": "/projects/:projectId/features2/:name",
@ -87,7 +85,6 @@ Array [
Object {
"component": [Function],
"flag": "P",
"layout": "main",
"menu": Object {},
"parent": "/projects",
"path": "/projects/:id/:activeTab",
@ -97,7 +94,6 @@ Array [
Object {
"component": [Function],
"flag": "P",
"layout": "main",
"menu": Object {},
"parent": "/projects",
"path": "/projects/:id",
@ -106,7 +102,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {
"mobile": true,
},
@ -116,7 +111,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/features",
"path": "/features/:activeTab/:name",
@ -125,7 +119,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {
"mobile": true,
},
@ -135,7 +128,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/applications",
"path": "/applications/:name",
@ -144,7 +136,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {
"advanced": true,
"mobile": true,
@ -156,7 +147,6 @@ Array [
Object {
"component": [Function],
"flag": "C",
"layout": "main",
"menu": Object {},
"parent": "/context",
"path": "/context/create",
@ -166,7 +156,6 @@ Array [
Object {
"component": [Function],
"flag": "C",
"layout": "main",
"menu": Object {},
"parent": "/context",
"path": "/context/edit/:name",
@ -176,7 +165,6 @@ Array [
Object {
"component": [Function],
"flag": "C",
"layout": "main",
"menu": Object {
"advanced": true,
"mobile": true,
@ -187,7 +175,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/strategies",
"path": "/strategies/create",
@ -196,7 +183,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/strategies",
"path": "/strategies/:name/edit",
@ -205,7 +191,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/strategies",
"path": "/strategies/:name",
@ -214,7 +199,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {
"advanced": true,
"mobile": true,
@ -225,7 +209,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/environments",
"path": "/environments/create",
@ -234,7 +217,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"path": "/environments/:id",
"title": "Edit",
@ -243,7 +225,6 @@ Array [
Object {
"component": [Function],
"flag": "EEA",
"layout": "main",
"menu": Object {
"advanced": true,
"mobile": true,
@ -254,7 +235,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/tag-types",
"path": "/tag-types/create",
@ -263,7 +243,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/tag-types",
"path": "/tag-types/edit/:name",
@ -272,7 +251,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {
"advanced": true,
"mobile": true,
@ -283,7 +261,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/addons",
"path": "/addons/create/:providerId",
@ -292,7 +269,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/addons",
"path": "/addons/edit/:addonId",
@ -302,7 +278,6 @@ Array [
Object {
"component": [Function],
"hidden": false,
"layout": "main",
"menu": Object {
"advanced": true,
"mobile": true,
@ -315,7 +290,6 @@ Array [
"component": [Function],
"flag": "SE",
"hidden": false,
"layout": "main",
"menu": Object {
"advanced": true,
"mobile": true,
@ -326,7 +300,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/history",
"path": "/history/:toggleName",
@ -335,7 +308,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {
"adminSettings": true,
},
@ -345,7 +317,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"path": "/archive",
"title": "Archived Toggles",
@ -353,7 +324,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/admin",
"path": "/admin/api/create-token",
@ -363,7 +333,6 @@ Array [
Object {
"component": [Function],
"flag": "RE",
"layout": "main",
"menu": Object {},
"path": "/admin/create-project-role",
"title": "Create",
@ -372,7 +341,6 @@ Array [
Object {
"component": [Function],
"flag": "RE",
"layout": "main",
"menu": Object {},
"path": "/admin/roles/:id/edit",
"title": "Edit",
@ -380,7 +348,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {
"advanced": true,
"mobile": true,
@ -392,7 +359,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {
"adminSettings": true,
},
@ -403,7 +369,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {},
"parent": "/admin",
"path": "/admin/create-user",
@ -412,7 +377,6 @@ Array [
},
Object {
"component": [Function],
"layout": "main",
"menu": Object {
"adminSettings": true,
},
@ -424,7 +388,6 @@ Array [
Object {
"component": [Function],
"flag": "RE",
"layout": "main",
"menu": Object {
"adminSettings": true,
},
@ -436,7 +399,6 @@ Array [
Object {
"component": [Function],
"hidden": false,
"layout": "main",
"menu": Object {},
"path": "/admin",
"title": "Admin",

View File

@ -46,18 +46,26 @@ import { EventHistoryPage } from '../history/EventHistoryPage/EventHistoryPage';
import { FeatureEventHistoryPage } from '../history/FeatureEventHistoryPage/FeatureEventHistoryPage';
import { CreateStrategy } from '../strategies/CreateStrategy/CreateStrategy';
import { EditStrategy } from '../strategies/EditStrategy/EditStrategy';
import { SegmentsList } from 'component/segments/SegmentList/SegmentList';
import { SegmentsList } from '../segments/SegmentList/SegmentList';
import { SplashPage } from '../splash/SplashPage/SplashPage';
export const routes = [
// Project
// Splash
{
path: '/splash/:splashId',
title: 'Unleash',
component: SplashPage,
type: 'protected',
menu: {},
},
// Project
{
path: '/projects/create',
parent: '/projects',
title: 'Create',
component: CreateProject,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -66,7 +74,6 @@ export const routes = [
title: ':id',
component: EditProject,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -75,7 +82,6 @@ export const routes = [
parent: '/archive',
component: RedirectArchive,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -84,7 +90,6 @@ export const routes = [
title: 'Copy',
component: CopyFeatureToggle,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -93,7 +98,6 @@ export const routes = [
title: 'Edit Feature',
component: EditFeature,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -102,7 +106,6 @@ export const routes = [
title: 'FeatureView',
component: FeatureView,
type: 'protected',
layout: 'main',
flags: E,
menu: {},
},
@ -112,7 +115,6 @@ export const routes = [
title: ':name',
component: FeatureView,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -121,7 +123,6 @@ export const routes = [
title: 'Create feature toggle',
component: CreateFeature,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -130,7 +131,6 @@ export const routes = [
title: ':name',
component: RedirectFeatureView,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -140,7 +140,6 @@ export const routes = [
component: Project,
flag: P,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -150,7 +149,6 @@ export const routes = [
component: Project,
flag: P,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -158,7 +156,6 @@ export const routes = [
title: 'Projects',
component: ProjectListNew,
type: 'protected',
layout: 'main',
menu: { mobile: true },
},
@ -169,7 +166,6 @@ export const routes = [
title: ':name',
component: RedirectFeatureView,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -177,7 +173,6 @@ export const routes = [
title: 'Feature Toggles',
component: FeatureToggleListContainer,
type: 'protected',
layout: 'main',
menu: { mobile: true },
},
@ -188,7 +183,6 @@ export const routes = [
parent: '/applications',
component: ApplicationEdit,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -196,7 +190,6 @@ export const routes = [
title: 'Applications',
component: ApplicationList,
type: 'protected',
layout: 'main',
menu: { mobile: true, advanced: true },
},
@ -207,7 +200,6 @@ export const routes = [
title: 'Create',
component: CreateContext,
type: 'protected',
layout: 'main',
flag: C,
menu: {},
},
@ -217,7 +209,6 @@ export const routes = [
title: ':name',
component: EditContext,
type: 'protected',
layout: 'main',
flag: C,
menu: {},
},
@ -227,7 +218,6 @@ export const routes = [
component: ContextList,
type: 'protected',
flag: C,
layout: 'main',
menu: { mobile: true, advanced: true },
},
@ -238,7 +228,6 @@ export const routes = [
parent: '/strategies',
component: CreateStrategy,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -247,7 +236,6 @@ export const routes = [
parent: '/strategies',
component: EditStrategy,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -256,7 +244,6 @@ export const routes = [
parent: '/strategies',
component: StrategyView,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -264,7 +251,6 @@ export const routes = [
title: 'Strategies',
component: StrategiesList,
type: 'protected',
layout: 'main',
menu: { mobile: true, advanced: true },
},
{
@ -273,7 +259,6 @@ export const routes = [
component: CreateEnvironment,
parent: '/environments',
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -281,7 +266,6 @@ export const routes = [
title: 'Edit',
component: EditEnvironment,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -289,7 +273,6 @@ export const routes = [
title: 'Environments',
component: EnvironmentList,
type: 'protected',
layout: 'main',
flag: EEA,
menu: { mobile: true, advanced: true },
},
@ -301,7 +284,6 @@ export const routes = [
title: 'Create',
component: CreateTagType,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -310,7 +292,6 @@ export const routes = [
title: ':name',
component: EditTagType,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -318,7 +299,6 @@ export const routes = [
title: 'Tag types',
component: TagTypeList,
type: 'protected',
layout: 'main',
menu: { mobile: true, advanced: true },
},
@ -329,7 +309,6 @@ export const routes = [
title: 'Create',
component: CreateAddon,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -338,7 +317,6 @@ export const routes = [
title: 'Edit',
component: EditAddon,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -347,7 +325,6 @@ export const routes = [
component: AddonList,
hidden: false,
type: 'protected',
layout: 'main',
menu: { mobile: true, advanced: true },
},
@ -359,7 +336,6 @@ export const routes = [
component: SegmentsList,
hidden: false,
type: 'protected',
layout: 'main',
menu: { mobile: true, advanced: true },
flag: SE,
},
@ -371,7 +347,6 @@ export const routes = [
parent: '/history',
component: FeatureEventHistoryPage,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -379,7 +354,6 @@ export const routes = [
title: 'Event History',
component: EventHistoryPage,
type: 'protected',
layout: 'main',
menu: { adminSettings: true },
},
@ -389,7 +363,6 @@ export const routes = [
title: 'Archived Toggles',
component: ArchiveListContainer,
type: 'protected',
layout: 'main',
menu: {},
},
@ -400,7 +373,6 @@ export const routes = [
title: 'API access',
component: CreateApiToken,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -408,7 +380,6 @@ export const routes = [
title: 'Create',
component: CreateProjectRole,
type: 'protected',
layout: 'main',
menu: {},
flag: RE,
},
@ -417,7 +388,6 @@ export const routes = [
title: 'Edit',
component: EditProjectRole,
type: 'protected',
layout: 'main',
menu: {},
flag: RE,
},
@ -426,7 +396,6 @@ export const routes = [
title: 'Edit',
component: EditUser,
type: 'protected',
layout: 'main',
menu: {},
hidden: true,
},
@ -436,7 +405,6 @@ export const routes = [
title: 'API access',
component: AdminApi,
type: 'protected',
layout: 'main',
menu: { mobile: true, advanced: true },
},
{
@ -445,7 +413,6 @@ export const routes = [
title: 'Users',
component: AdminUsers,
type: 'protected',
layout: 'main',
menu: { adminSettings: true },
},
{
@ -454,7 +421,6 @@ export const routes = [
title: 'Users',
component: CreateUser,
type: 'protected',
layout: 'main',
menu: {},
},
{
@ -463,7 +429,6 @@ export const routes = [
title: 'Single Sign-On',
component: AuthSettings,
type: 'protected',
layout: 'main',
menu: { adminSettings: true },
},
{
@ -472,7 +437,6 @@ export const routes = [
component: AdminInvoice,
hidden: true,
type: 'protected',
layout: 'main',
menu: { adminSettings: true },
},
{
@ -481,7 +445,6 @@ export const routes = [
title: 'Project Roles',
component: ProjectRoles,
type: 'protected',
layout: 'main',
flag: RE,
menu: { adminSettings: true },
},
@ -491,7 +454,6 @@ export const routes = [
component: Admin,
hidden: false,
type: 'protected',
layout: 'main',
menu: {},
},
/* If you update this route path, make sure you update the path in SWRProvider.tsx */
@ -502,7 +464,6 @@ export const routes = [
component: Login,
type: 'unprotected',
hidden: true,
layout: 'standalone',
menu: {},
},
/* If you update this route path, make sure you update the path in SWRProvider.tsx */
@ -512,7 +473,6 @@ export const routes = [
hidden: true,
component: NewUser,
type: 'unprotected',
layout: 'standalone',
menu: {},
},
/* If you update this route path, make sure you update the path in SWRProvider.tsx */
@ -522,7 +482,6 @@ export const routes = [
hidden: true,
component: ResetPassword,
type: 'unprotected',
layout: 'standalone',
menu: {},
},
{
@ -531,7 +490,6 @@ export const routes = [
hidden: true,
component: ForgottenPassword,
type: 'unprotected',
layout: 'standalone',
menu: {},
},
];

View File

@ -22,7 +22,7 @@ interface IProjectInfoProps {
memberCount: number;
featureCount: number;
health: number;
description: string;
description?: string;
}
const ProjectInfo = ({
@ -42,10 +42,11 @@ const ProjectInfo = ({
}
const LONG_DESCRIPTION = 100;
const isShortDescription =
!description || description.length < LONG_DESCRIPTION;
const permissionButtonClass = classnames({
[styles.permissionButtonShortDesc]:
description.length < LONG_DESCRIPTION,
[styles.permissionButtonShortDesc]: isShortDescription,
});
const permissionButton = (
<PermissionIconButton
@ -70,9 +71,7 @@ const ProjectInfo = ({
condition={Boolean(description)}
show={
<ConditionallyRender
condition={
description.length < LONG_DESCRIPTION
}
condition={isShortDescription}
show={
<p
data-loading
@ -113,7 +112,7 @@ const ProjectInfo = ({
}
/>
<ConditionallyRender
condition={description.length < LONG_DESCRIPTION}
condition={isShortDescription}
show={permissionButton}
/>
</div>

View File

@ -0,0 +1,72 @@
import { Switch, Route, useHistory, Redirect } from 'react-router-dom';
import { SplashPageEnvironments } from '../SplashPageEnvironments/SplashPageEnvironments';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import useSplashApi from 'hooks/api/actions/useSplashApi/useSplashApi';
import { SplashPageOperators } from 'component/splash/SplashPageOperators/SplashPageOperators';
import { useEffect } from 'react';
import { useAuthSplash } from 'hooks/api/getters/useAuth/useAuthSplash';
// All known splash IDs.
export const splashIds = ['environments', 'operators'] as const;
// Active splash IDs that may be shown to the user.
export const activeSplashIds: SplashId[] = ['operators'];
export type SplashId = typeof splashIds[number];
export const SplashPage = () => {
const splashId = useRequiredPathParam('splashId');
const isKnownId = isKnownSplashId(splashId);
const { refetchSplash } = useAuthSplash();
const { setSplashSeen } = useSplashApi();
// Close the splash "modal" on escape.
useNavigationOnKeydown('Escape', '/');
// Mark the splash ID as seen.
useEffect(() => {
if (splashId && isKnownId) {
setSplashSeen(splashId)
.then(() => refetchSplash())
.catch(console.warn);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [splashId, isKnownId]);
if (!isKnownId) {
return null;
}
return (
<Switch>
<Route path="/splash/environments">
<SplashPageEnvironments />
</Route>
<Route path="/splash/operators">
<SplashPageOperators />
</Route>
<Route>
<Redirect to="/" />
</Route>
</Switch>
);
};
const useNavigationOnKeydown = (key: string, path: string) => {
const { push } = useHistory();
useEffect(() => {
const handler = (event: KeyboardEvent) => {
if (event.code === key) {
push(path);
}
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [key, path, push]);
};
const isKnownSplashId = (value: string): value is SplashId => {
return (splashIds as Readonly<string[]>).includes(value);
};

View File

@ -1,33 +1,25 @@
import Splash from '../Splash/Splash';
import EnvironmentSplashPage from './EnvironmentSplashPage/EnvironmentSplashPage';
import { SplashPageEnvironmentsContent } from 'component/splash/SplashPageEnvironments/SplashPageEnvironmentsContent/SplashPageEnvironmentsContent';
import { SplashPageEnvironmentsContainer } from 'component/splash/SplashPageEnvironments/SplashPageEnvironmentsContainer/SplashPageEnvironmentsContainer';
import { VpnKey, CloudCircle } from '@material-ui/icons';
import { useStyles } from './EnvironmentSplash.styles';
import { ReactComponent as Logo1 } from '../../../assets/img/splash_env1.svg';
import { ReactComponent as Logo2 } from '../../../assets/img/splash_env2.svg';
import { useEffect } from 'react';
import useSplashApi from '../../../hooks/api/actions/useSplashApi/useSplashApi';
import { useStyles } from 'component/splash/SplashPageEnvironments/SplashPageEnvironments.styles';
import { ReactComponent as Logo1 } from 'assets/img/splash_env1.svg';
import { ReactComponent as Logo2 } from 'assets/img/splash_env2.svg';
import { useHistory } from 'react-router-dom';
interface IEnvironmentSplashProps {
onFinish: (status: boolean) => void;
}
const ENVIRONMENT_SPLASH_ID = 'environment';
const EnvironmentSplash = ({ onFinish }: IEnvironmentSplashProps) => {
export const SplashPageEnvironments = () => {
const styles = useStyles();
const { setSplashSeen } = useSplashApi();
const { push } = useHistory();
useEffect(() => {
setSplashSeen(ENVIRONMENT_SPLASH_ID);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onFinish = () => {
push('/');
};
return (
<>
<Splash
<SplashPageEnvironmentsContent
onFinish={onFinish}
components={[
<EnvironmentSplashPage
<SplashPageEnvironmentsContainer
key={1}
title={
<h2 className={styles.title}>
@ -61,7 +53,7 @@ const EnvironmentSplash = ({ onFinish }: IEnvironmentSplashProps) => {
}
image={<CloudCircle className={styles.icon} />}
/>,
<EnvironmentSplashPage
<SplashPageEnvironmentsContainer
key={2}
title={
<h2 className={styles.title}>
@ -79,7 +71,7 @@ const EnvironmentSplash = ({ onFinish }: IEnvironmentSplashProps) => {
}
image={<Logo1 className={styles.logo} />}
/>,
<EnvironmentSplashPage
<SplashPageEnvironmentsContainer
key={3}
title={
<h2 className={styles.title}>
@ -99,7 +91,7 @@ const EnvironmentSplash = ({ onFinish }: IEnvironmentSplashProps) => {
}
image={<Logo2 className={styles.logo} />}
/>,
<EnvironmentSplashPage
<SplashPageEnvironmentsContainer
key={4}
title={
<h2 className={styles.title}>
@ -125,7 +117,7 @@ const EnvironmentSplash = ({ onFinish }: IEnvironmentSplashProps) => {
}
image={<VpnKey className={styles.icon} />}
/>,
<EnvironmentSplashPage
<SplashPageEnvironmentsContainer
key={5}
title={
<h2 className={styles.title}>Want to know more?</h2>
@ -202,5 +194,3 @@ const EnvironmentSplash = ({ onFinish }: IEnvironmentSplashProps) => {
</>
);
};
export default EnvironmentSplash;

View File

@ -1,18 +1,18 @@
import React from 'react';
interface EnvironmentSplashPageProps {
interface ISplashPageEnvironmentsContainerProps {
title: React.ReactNode;
topDescription: React.ReactNode;
image?: React.ReactNode;
bottomDescription?: React.ReactNode;
}
const EnvironmentSplashPage = ({
export const SplashPageEnvironmentsContainer = ({
title,
topDescription,
image,
bottomDescription,
}: EnvironmentSplashPageProps) => {
}: ISplashPageEnvironmentsContainerProps) => {
return (
<div>
{title}
@ -22,5 +22,3 @@ const EnvironmentSplashPage = ({
</div>
);
};
export default EnvironmentSplashPage;

View File

@ -4,6 +4,7 @@ export const useStyles = makeStyles(theme => ({
splashMainContainer: {
backgroundColor: theme.palette.primary.light,
width: '100%',
minHeight: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',

View File

@ -1,23 +1,22 @@
import React, { Fragment, useState } from 'react';
import { Button, IconButton } from '@material-ui/core';
import { useStyles } from './Splash.styles';
import { useStyles } from 'component/splash/SplashPageEnvironments/SplashPageEnvironmentsContent/SplashPageEnvironmentsContent.styles';
import {
CloseOutlined,
FiberManualRecord,
FiberManualRecordOutlined,
} from '@material-ui/icons';
import ConditionallyRender from '../ConditionallyRender';
import { CLOSE_SPLASH } from '../../../testIds';
import ConditionallyRender from 'component/common/ConditionallyRender';
import { CLOSE_SPLASH } from 'testIds';
interface ISplashProps {
interface ISplashPageEnvironmentsContentProps {
components: React.ReactNode[];
onFinish: (status: boolean) => void;
}
const Splash: React.FC<ISplashProps> = ({
components,
onFinish,
}: ISplashProps) => {
export const SplashPageEnvironmentsContent: React.FC<
ISplashPageEnvironmentsContentProps
> = ({ components, onFinish }: ISplashPageEnvironmentsContentProps) => {
const styles = useStyles();
const [counter, setCounter] = useState(0);
@ -76,7 +75,7 @@ const Splash: React.FC<ISplashProps> = ({
onClick={onClose}
data-test={CLOSE_SPLASH}
>
<CloseOutlined />
<CloseOutlined titleAccess="Close" />
</IconButton>
</div>
{components[counter]}
@ -109,5 +108,3 @@ const Splash: React.FC<ISplashProps> = ({
</div>
);
};
export default Splash;

View File

@ -0,0 +1,78 @@
import { makeStyles } from '@material-ui/core/styles';
export const useStyles = makeStyles(theme => ({
container: {
backgroundColor: theme.palette.primary.light,
minHeight: '100vh',
padding: '1rem',
display: 'grid',
gap: '1rem',
alignItems: 'center',
alignContent: 'center',
justifyContent: 'center',
gridTemplateColumns: 'minmax(0,auto)',
fontWeight: theme.fontWeight.thin,
},
content: {
position: 'relative',
padding: '2rem',
borderRadius: '0.5rem',
backgroundColor: theme.palette.primary.main,
color: 'white',
[theme.breakpoints.up('md')]: {
padding: '4rem',
},
},
header: {
textAlign: 'center',
},
footer: {
display: 'grid',
gap: '2rem',
textAlign: 'center',
justifyItems: 'center',
},
body: {
margin: '2rem 0',
padding: '2rem 0',
borderTop: '1px solid',
borderBottom: '1px solid',
borderTopColor: theme.palette.primary.light,
borderBottomColor: theme.palette.primary.light,
},
close: {
position: 'absolute',
top: 0,
right: 0,
color: 'inherit',
},
title: {
fontWeight: 'inherit',
},
ingress: {
maxWidth: '32rem',
margin: '1.5rem auto 0 auto',
},
list: {
padding: '1rem 0',
[theme.breakpoints.up('md')]: {
padding: '1rem 2rem',
},
'& li + li': {
marginTop: '0.25rem',
},
'& strong': {
padding: '0 .2rem',
fontSize: theme.fontSizes.smallBody,
fontWeight: 'inherit',
backgroundColor: 'rgba(0, 0, 0, 0.2)',
},
},
link: {
color: 'inherit',
},
button: {
background: 'white',
color: theme.palette.primary.main,
},
}));

View File

@ -0,0 +1,91 @@
import { useStyles } from 'component/splash/SplashPageOperators/SplashPageOperators.styles';
import { Link, useHistory } from 'react-router-dom';
import { Button, IconButton } from '@material-ui/core';
import { CloseOutlined } from '@material-ui/icons';
import { OperatorUpgradeAlert } from 'component/common/OperatorUpgradeAlert/OperatorUpgradeAlert';
export const SplashPageOperators = () => {
const { push } = useHistory();
const styles = useStyles();
return (
<section className={styles.container}>
<div className={styles.content}>
<header className={styles.header}>
<h1 className={styles.title}>New strategy operators</h1>
<IconButton
className={styles.close}
onClick={() => push('/')}
>
<CloseOutlined titleAccess="Close" />
</IconButton>
<p className={styles.ingress}>
We've added some new feature strategy constraint
operators. Fine-tune your feature targeting like never
before.
</p>
</header>
<div className={styles.body}>
<p>For example:</p>
<ul className={styles.list}>
<li>
<span>Toggle features at dates: </span>
<span>
<strong>DATE_BEFORE</strong>{' '}
<strong>DATE_AFTER</strong>
</span>
</li>
<li>
<span>Toggle features for versions: </span>
<span>
<strong>SEMVER_EQ</strong>{' '}
<strong>SEMVER_GT</strong>{' '}
<strong>SEMVER_LT</strong>
</span>
</li>
<li>
<span>Toggle features for strings: </span>
<span>
<strong>STR_CONTAINS</strong>{' '}
<strong>STR_ENDS_WITH</strong>{' '}
<strong>STR_STARTS_WITH</strong>
</span>
</li>
<li>
<span>Toggle features for numbers: </span>
<span>
<strong>NUM_GT</strong> <strong>NUM_GTE</strong>{' '}
<strong>NUM_LT</strong> <strong>NUM_LTE</strong>
</span>
</li>
</ul>
</div>
<footer className={styles.footer}>
<p>
<a
href="https://docs.getunleash.io/advanced/strategy_constraints#numeric-operators"
target="_blank"
rel="noreferrer"
className={styles.link}
>
Read all about operators in our in-depth{' '}
<strong>docs</strong>
</a>
.
</p>
<p>
<Button
className={styles.button}
variant="contained"
component={Link}
to="/"
>
Fine, whatever, I have work to do!
</Button>
</p>
</footer>
</div>
<OperatorUpgradeAlert />
</section>
);
};

View File

@ -0,0 +1,52 @@
import { useAuthSplash } from 'hooks/api/getters/useAuth/useAuthSplash';
import { useLocation, Redirect } from 'react-router-dom';
import { matchPath } from 'react-router';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { IFlags } from 'interfaces/uiConfig';
import { IAuthSplash } from 'hooks/api/getters/useAuth/useAuthEndpoint';
import {
activeSplashIds,
SplashId,
} from 'component/splash/SplashPage/SplashPage';
export const SplashPageRedirect = () => {
const { pathname } = useLocation();
const { splash } = useAuthSplash();
const { uiConfig, loading } = useUiConfig();
if (!splash || !uiConfig || loading) {
// Wait for everything to load.
return null;
}
if (matchPath(pathname, { path: '/splash/:splashId' })) {
// We've already redirected to the splash page.
return null;
}
// Find the splash page to show (if any).
const showSplashId = activeSplashIds.find(splashId => {
return (
isSplashRelevant(splashId, uiConfig.flags) &&
!hasSeenSplashId(splashId, splash)
);
});
if (!showSplashId) {
return null;
}
return <Redirect to={`/splash/${showSplashId}`} />;
};
const hasSeenSplashId = (splashId: SplashId, splash: IAuthSplash): boolean => {
return Boolean(splash[splashId]);
};
const isSplashRelevant = (splashId: SplashId, flags: IFlags): boolean => {
if (splashId === 'operators') {
return Boolean(flags.C || flags.CO);
}
return true;
};

View File

@ -163,12 +163,14 @@ export const StrategiesList = () => {
const reactivateButton = (strategy: ICustomStrategy) => (
<Tooltip title="Reactivate activation strategy">
<PermissionIconButton
onClick={() => onReactivateStrategy(strategy)}
permission={UPDATE_STRATEGY}
>
<VisibilityOff />
</PermissionIconButton>
<div>
<PermissionIconButton
onClick={() => onReactivateStrategy(strategy)}
permission={UPDATE_STRATEGY}
>
<VisibilityOff titleAccess="Reactivate" />
</PermissionIconButton>
</div>
</Tooltip>
);
@ -179,7 +181,7 @@ export const StrategiesList = () => {
<Tooltip title="You cannot deprecate the default strategy">
<div>
<IconButton disabled>
<Visibility />
<Visibility titleAccess="Deprecate strategy" />
</IconButton>
</div>
</Tooltip>
@ -190,7 +192,7 @@ export const StrategiesList = () => {
onClick={() => onDeprecateStrategy(strategy)}
permission={UPDATE_STRATEGY}
>
<Visibility />
<Visibility titleAccess="Deprecate strategy" />
</PermissionIconButton>
</div>
}

View File

@ -159,14 +159,17 @@ exports[`renders correctly with one strategy 1`] = `
className="MuiIconButton-label"
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
role="img"
viewBox="0 0 24 24"
>
<path
d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"
/>
<title>
Deprecate strategy
</title>
</svg>
</span>
<span
@ -431,14 +434,17 @@ exports[`renders correctly with one strategy without permissions 1`] = `
className="MuiIconButton-label"
>
<svg
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
role="img"
viewBox="0 0 24 24"
>
<path
d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"
/>
<title>
Deprecate strategy
</title>
</svg>
</span>
<span

View File

@ -15,7 +15,7 @@ export interface IProject {
members: number;
version: string;
name: string;
description: string;
description?: string;
environments: string[];
health: number;
features: IFeatureToggleListItem[];

View File

@ -6,7 +6,6 @@ interface IRoute {
title?: string;
component: React.ComponentType;
type: string;
layout: string;
hidden?: boolean;
flag?: string;
parent?: string;

View File

@ -2049,7 +2049,14 @@
dependencies:
"@types/react" "*"
"@types/react-dom@17.0.13", "@types/react-dom@>=16.9.0":
"@types/react-dom@17.0.14":
version "17.0.14"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.14.tgz#c8f917156b652ddf807711f5becbd2ab018dea9f"
integrity sha512-H03xwEP1oXmSfl3iobtmQ/2dHF5aBHr8aUMwyGZya6OW45G+xtdzmq6HkncefiBt5JU8DVyaWl/nWZbjZCnzAQ==
dependencies:
"@types/react" "*"
"@types/react-dom@>=16.9.0":
version "17.0.13"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.13.tgz#a3323b974ee4280070982b3112351bb1952a7809"
integrity sha512-wEP+B8hzvy6ORDv1QBhcQia4j6ea4SFIBttHYpXKPFZRviBvknq0FRh3VrIxeXUmsPkwuXVZrVGG7KUVONmXCQ==
@ -2103,7 +2110,16 @@
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/react@17.0.40", "@types/react@>=16.9.0":
"@types/react@17.0.41":
version "17.0.41"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.41.tgz#6e179590d276394de1e357b3f89d05d7d3da8b85"
integrity sha512-chYZ9ogWUodyC7VUTRBfblysKLjnohhFY9bGLwvnUFFy48+vB9DikmB3lW0qTFmBcKSzmdglcvkHK71IioOlDA==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/react@>=16.9.0":
version "17.0.40"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.40.tgz#dc010cee6254d5239a138083f3799a16638e6bad"
integrity sha512-UrXhD/JyLH+W70nNSufXqMZNuUD2cXHu6UjCllC6pmOQgBX4SGXOH8fjRka0O0Ee0HrFxapDD8Bwn81Kmiz6jQ==