mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-04 00:18:40 +01:00
feat: advancedPlayground flag used only for runtime control (#4262)
This commit is contained in:
parent
1f21770977
commit
593f83d5d3
@ -133,7 +133,14 @@ exports[`returns all baseRoutes 1`] = `
|
|||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"component": [Function],
|
"component": {
|
||||||
|
"$$typeof": Symbol(react.lazy),
|
||||||
|
"_init": [Function],
|
||||||
|
"_payload": {
|
||||||
|
"_result": [Function],
|
||||||
|
"_status": -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"menu": {
|
"menu": {
|
||||||
"mobile": true,
|
"mobile": true,
|
||||||
|
@ -1,15 +1,3 @@
|
|||||||
import { lazy } from 'react';
|
import { lazy } from 'react';
|
||||||
import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig';
|
|
||||||
|
|
||||||
export const LazyLegacyPlayground = lazy(() => import('./Playground'));
|
export const LazyPlayground = lazy(() => import('./AdvancedPlayground'));
|
||||||
export const LazyAdvancedPlayground = lazy(
|
|
||||||
() => import('./AdvancedPlayground')
|
|
||||||
);
|
|
||||||
|
|
||||||
export const LazyPlayground = () => {
|
|
||||||
const { uiConfig } = useUiConfig();
|
|
||||||
|
|
||||||
if (uiConfig.flags.advancedPlayground) return <LazyAdvancedPlayground />;
|
|
||||||
|
|
||||||
return <LazyLegacyPlayground />;
|
|
||||||
};
|
|
||||||
|
@ -1,223 +0,0 @@
|
|||||||
import { FormEventHandler, useEffect, useState, VFC } from 'react';
|
|
||||||
import { useSearchParams } from 'react-router-dom';
|
|
||||||
import { Box, Paper, useMediaQuery, useTheme } from '@mui/material';
|
|
||||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
|
||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
|
||||||
import useToast from 'hooks/useToast';
|
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
|
||||||
import { PlaygroundResultsTable } from './PlaygroundResultsTable/PlaygroundResultsTable';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import { usePlaygroundApi } from 'hooks/api/actions/usePlayground/usePlayground';
|
|
||||||
import { PlaygroundResponseSchema } from 'openapi';
|
|
||||||
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
|
|
||||||
import { PlaygroundForm } from './PlaygroundForm/PlaygroundForm';
|
|
||||||
import {
|
|
||||||
resolveDefaultEnvironment,
|
|
||||||
resolveProjects,
|
|
||||||
resolveResultsWidth,
|
|
||||||
} from './playground.utils';
|
|
||||||
import { PlaygroundGuidance } from './PlaygroundGuidance/PlaygroundGuidance';
|
|
||||||
import { PlaygroundGuidancePopper } from './PlaygroundGuidancePopper/PlaygroundGuidancePopper';
|
|
||||||
import Loader from '../../common/Loader/Loader';
|
|
||||||
|
|
||||||
export const Playground: VFC<{}> = () => {
|
|
||||||
const { environments: availableEnvironments } = useEnvironments();
|
|
||||||
const theme = useTheme();
|
|
||||||
const matches = useMediaQuery(theme.breakpoints.down('lg'));
|
|
||||||
|
|
||||||
const [environments, setEnvironments] = useState<string[]>([]);
|
|
||||||
const [projects, setProjects] = useState<string[]>([]);
|
|
||||||
const [context, setContext] = useState<string>();
|
|
||||||
const [results, setResults] = useState<
|
|
||||||
PlaygroundResponseSchema | undefined
|
|
||||||
>();
|
|
||||||
const { setToastData } = useToast();
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
const { evaluatePlayground, loading } = usePlaygroundApi();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setEnvironments([resolveDefaultEnvironment(availableEnvironments)]);
|
|
||||||
}, [JSON.stringify(availableEnvironments)]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Load initial values from URL
|
|
||||||
try {
|
|
||||||
const environmentFromUrl = searchParams.get('environment');
|
|
||||||
if (environmentFromUrl) {
|
|
||||||
setEnvironments([environmentFromUrl]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let projectsArray: string[];
|
|
||||||
let projectsFromUrl = searchParams.get('projects');
|
|
||||||
if (projectsFromUrl) {
|
|
||||||
projectsArray = projectsFromUrl.split(',');
|
|
||||||
setProjects(projectsArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
let contextFromUrl = searchParams.get('context');
|
|
||||||
if (contextFromUrl) {
|
|
||||||
contextFromUrl = decodeURI(contextFromUrl);
|
|
||||||
setContext(contextFromUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
const makePlaygroundRequest = async () => {
|
|
||||||
if (environmentFromUrl && contextFromUrl) {
|
|
||||||
await evaluatePlaygroundContext(
|
|
||||||
environmentFromUrl,
|
|
||||||
projectsArray || '*',
|
|
||||||
contextFromUrl
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
makePlaygroundRequest();
|
|
||||||
} catch (error) {
|
|
||||||
setToastData({
|
|
||||||
type: 'error',
|
|
||||||
title: `Failed to parse URL parameters: ${formatUnknownError(
|
|
||||||
error
|
|
||||||
)}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const evaluatePlaygroundContext = async (
|
|
||||||
environment: string,
|
|
||||||
projects: string[] | string,
|
|
||||||
context: string | undefined,
|
|
||||||
action?: () => void
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const parsedContext = JSON.parse(context || '{}');
|
|
||||||
const response = await evaluatePlayground({
|
|
||||||
environment,
|
|
||||||
projects: resolveProjects(projects),
|
|
||||||
context: {
|
|
||||||
appName: 'playground',
|
|
||||||
...parsedContext,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (action && typeof action === 'function') {
|
|
||||||
action();
|
|
||||||
}
|
|
||||||
setResults(response);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
setToastData({
|
|
||||||
type: 'error',
|
|
||||||
title: `Error parsing context: ${formatUnknownError(error)}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmit: FormEventHandler<HTMLFormElement> = async event => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
await evaluatePlaygroundContext(
|
|
||||||
environments[0],
|
|
||||||
projects,
|
|
||||||
context,
|
|
||||||
setURLParameters
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const setURLParameters = () => {
|
|
||||||
searchParams.set('context', encodeURI(context || '')); // always set because of native validation
|
|
||||||
searchParams.set('environment', environments[0]);
|
|
||||||
if (
|
|
||||||
Array.isArray(projects) &&
|
|
||||||
projects.length > 0 &&
|
|
||||||
!(projects.length === 1 && projects[0] === '*')
|
|
||||||
) {
|
|
||||||
searchParams.set('projects', projects.join(','));
|
|
||||||
} else {
|
|
||||||
searchParams.delete('projects');
|
|
||||||
}
|
|
||||||
setSearchParams(searchParams);
|
|
||||||
};
|
|
||||||
|
|
||||||
const formWidth = results && !matches ? '35%' : 'auto';
|
|
||||||
const resultsWidth = resolveResultsWidth(matches, results);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageContent
|
|
||||||
header={
|
|
||||||
<PageHeader
|
|
||||||
title="Unleash playground"
|
|
||||||
actions={<PlaygroundGuidancePopper />}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
disableLoading
|
|
||||||
bodyClass={'no-padding'}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: !matches ? 'row' : 'column',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
background: theme.palette.background.elevation2,
|
|
||||||
borderBottomLeftRadius: theme.shape.borderRadiusMedium,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Paper
|
|
||||||
elevation={0}
|
|
||||||
sx={{
|
|
||||||
px: 4,
|
|
||||||
py: 3,
|
|
||||||
mb: 4,
|
|
||||||
mt: 2,
|
|
||||||
background: theme.palette.background.elevation2,
|
|
||||||
transition: 'width 0.4s ease',
|
|
||||||
minWidth: matches ? 'auto' : '500px',
|
|
||||||
width: formWidth,
|
|
||||||
position: 'sticky',
|
|
||||||
top: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PlaygroundForm
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
context={context}
|
|
||||||
setContext={setContext}
|
|
||||||
availableEnvironments={availableEnvironments}
|
|
||||||
projects={projects}
|
|
||||||
environments={environments}
|
|
||||||
setProjects={setProjects}
|
|
||||||
setEnvironments={setEnvironments}
|
|
||||||
/>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
sx={theme => ({
|
|
||||||
width: resultsWidth,
|
|
||||||
transition: 'width 0.4s ease',
|
|
||||||
padding: theme.spacing(4, 2),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={loading}
|
|
||||||
show={<Loader />}
|
|
||||||
elseShow={
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={Boolean(results)}
|
|
||||||
show={
|
|
||||||
<PlaygroundResultsTable
|
|
||||||
loading={loading}
|
|
||||||
features={results?.features}
|
|
||||||
input={results?.input}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
elseShow={<PlaygroundGuidance />}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</PageContent>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Playground;
|
|
@ -1,303 +0,0 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
|
||||||
import { useSearchParams } from 'react-router-dom';
|
|
||||||
import {
|
|
||||||
SortingRule,
|
|
||||||
useFlexLayout,
|
|
||||||
useGlobalFilter,
|
|
||||||
useSortBy,
|
|
||||||
useTable,
|
|
||||||
} from 'react-table';
|
|
||||||
|
|
||||||
import { TablePlaceholder, VirtualizedTable } from 'component/common/Table';
|
|
||||||
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
|
||||||
import { sortTypes } from 'utils/sortTypes';
|
|
||||||
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import { Search } from 'component/common/Search/Search';
|
|
||||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
|
||||||
import { useSearch } from 'hooks/useSearch';
|
|
||||||
import { createLocalStorage } from 'utils/createLocalStorage';
|
|
||||||
import { FeatureStatusCell } from './FeatureStatusCell/FeatureStatusCell';
|
|
||||||
import { PlaygroundFeatureSchema, PlaygroundRequestSchema } from 'openapi';
|
|
||||||
import { Box, Typography, useMediaQuery, useTheme } from '@mui/material';
|
|
||||||
import useLoading from 'hooks/useLoading';
|
|
||||||
import { VariantCell } from './VariantCell/VariantCell';
|
|
||||||
import { FeatureResultInfoPopoverCell } from './FeatureResultInfoPopoverCell/FeatureResultInfoPopoverCell';
|
|
||||||
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
|
|
||||||
|
|
||||||
const defaultSort: SortingRule<string> = { id: 'name' };
|
|
||||||
const { value, setValue } = createLocalStorage(
|
|
||||||
'PlaygroundResultsTable:v1',
|
|
||||||
defaultSort
|
|
||||||
);
|
|
||||||
|
|
||||||
interface IPlaygroundResultsTableProps {
|
|
||||||
features?: PlaygroundFeatureSchema[];
|
|
||||||
input?: PlaygroundRequestSchema;
|
|
||||||
loading: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PlaygroundResultsTable = ({
|
|
||||||
features,
|
|
||||||
input,
|
|
||||||
loading,
|
|
||||||
}: IPlaygroundResultsTableProps) => {
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
const ref = useLoading(loading);
|
|
||||||
const [searchValue, setSearchValue] = useState(
|
|
||||||
searchParams.get('search') || ''
|
|
||||||
);
|
|
||||||
const theme = useTheme();
|
|
||||||
const isExtraSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
|
||||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
|
||||||
|
|
||||||
const COLUMNS = useMemo(() => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
Header: 'Name',
|
|
||||||
accessor: 'name',
|
|
||||||
searchable: true,
|
|
||||||
minWidth: 160,
|
|
||||||
Cell: ({ value, row: { original } }: any) => (
|
|
||||||
<LinkCell
|
|
||||||
title={value}
|
|
||||||
to={`/projects/${original?.projectId}/features/${value}`}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: 'Project ID',
|
|
||||||
accessor: 'projectId',
|
|
||||||
sortType: 'alphanumeric',
|
|
||||||
filterName: 'projectId',
|
|
||||||
searchable: true,
|
|
||||||
maxWidth: 170,
|
|
||||||
Cell: ({ value }: any) => (
|
|
||||||
<LinkCell title={value} to={`/projects/${value}`} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: 'Variant',
|
|
||||||
id: 'variant',
|
|
||||||
accessor: 'variant.name',
|
|
||||||
sortType: 'alphanumeric',
|
|
||||||
filterName: 'variant',
|
|
||||||
searchable: true,
|
|
||||||
maxWidth: 200,
|
|
||||||
Cell: ({
|
|
||||||
value,
|
|
||||||
row: {
|
|
||||||
original: { variant, feature, variants, isEnabled },
|
|
||||||
},
|
|
||||||
}: any) => (
|
|
||||||
<VariantCell
|
|
||||||
variant={variant?.enabled ? value : ''}
|
|
||||||
variants={variants}
|
|
||||||
feature={feature}
|
|
||||||
isEnabled={isEnabled}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'isEnabled',
|
|
||||||
Header: 'isEnabled',
|
|
||||||
filterName: 'isEnabled',
|
|
||||||
accessor: (row: PlaygroundFeatureSchema) =>
|
|
||||||
row?.isEnabled
|
|
||||||
? 'true'
|
|
||||||
: row?.strategies?.result === 'unknown'
|
|
||||||
? 'unknown'
|
|
||||||
: 'false',
|
|
||||||
Cell: ({ row }: any) => (
|
|
||||||
<FeatureStatusCell feature={row.original} />
|
|
||||||
),
|
|
||||||
sortType: 'playgroundResultState',
|
|
||||||
maxWidth: 120,
|
|
||||||
sortInverted: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: '',
|
|
||||||
maxWidth: 70,
|
|
||||||
id: 'info',
|
|
||||||
Cell: ({ row }: any) => (
|
|
||||||
<FeatureResultInfoPopoverCell
|
|
||||||
feature={row.original}
|
|
||||||
input={input}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [input]);
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: searchedData,
|
|
||||||
getSearchText,
|
|
||||||
getSearchContext,
|
|
||||||
} = useSearch(COLUMNS, searchValue, features || []);
|
|
||||||
|
|
||||||
const data = useMemo(() => {
|
|
||||||
return loading
|
|
||||||
? Array(5).fill({
|
|
||||||
name: 'Feature name',
|
|
||||||
projectId: 'FeatureProject',
|
|
||||||
variant: { name: 'FeatureVariant', variants: [] },
|
|
||||||
enabled: true,
|
|
||||||
})
|
|
||||||
: searchedData;
|
|
||||||
}, [searchedData, loading]);
|
|
||||||
|
|
||||||
const [initialState] = useState(() => ({
|
|
||||||
sortBy: [
|
|
||||||
{
|
|
||||||
id: searchParams.get('sort') || value.id,
|
|
||||||
desc: searchParams.has('order')
|
|
||||||
? searchParams.get('order') === 'desc'
|
|
||||||
: value.desc,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}));
|
|
||||||
|
|
||||||
const {
|
|
||||||
headerGroups,
|
|
||||||
rows,
|
|
||||||
state: { sortBy },
|
|
||||||
prepareRow,
|
|
||||||
setHiddenColumns,
|
|
||||||
} = useTable(
|
|
||||||
{
|
|
||||||
initialState,
|
|
||||||
columns: COLUMNS as any,
|
|
||||||
data: data as any,
|
|
||||||
sortTypes,
|
|
||||||
autoResetGlobalFilter: false,
|
|
||||||
autoResetHiddenColumns: false,
|
|
||||||
autoResetSortBy: false,
|
|
||||||
disableSortRemove: true,
|
|
||||||
disableMultiSort: true,
|
|
||||||
defaultColumn: {
|
|
||||||
Cell: HighlightCell,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
useGlobalFilter,
|
|
||||||
useFlexLayout,
|
|
||||||
useSortBy
|
|
||||||
);
|
|
||||||
|
|
||||||
useConditionallyHiddenColumns(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
condition: isExtraSmallScreen,
|
|
||||||
columns: ['variant'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
condition: isSmallScreen,
|
|
||||||
columns: ['projectId'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
setHiddenColumns,
|
|
||||||
COLUMNS
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (loading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tableState: Record<string, string> =
|
|
||||||
Object.fromEntries(searchParams);
|
|
||||||
tableState.sort = sortBy[0].id;
|
|
||||||
if (sortBy[0].desc) {
|
|
||||||
tableState.order = 'desc';
|
|
||||||
} else if (tableState.order) {
|
|
||||||
delete tableState.order;
|
|
||||||
}
|
|
||||||
if (searchValue) {
|
|
||||||
tableState.search = searchValue;
|
|
||||||
} else {
|
|
||||||
delete tableState.search;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSearchParams(tableState, {
|
|
||||||
replace: true,
|
|
||||||
});
|
|
||||||
setValue({ id: sortBy[0].id, desc: sortBy[0].desc || false });
|
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- don't re-render after search params change
|
|
||||||
}, [loading, sortBy, searchValue]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
mb: 3,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="subtitle1" sx={{ ml: 1 }}>
|
|
||||||
{features !== undefined && !loading
|
|
||||||
? `Results (${
|
|
||||||
rows.length < data.length
|
|
||||||
? `${rows.length} of ${data.length}`
|
|
||||||
: data.length
|
|
||||||
})`
|
|
||||||
: 'Results'}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Search
|
|
||||||
initialValue={searchValue}
|
|
||||||
onChange={setSearchValue}
|
|
||||||
hasFilters
|
|
||||||
getSearchContext={getSearchContext}
|
|
||||||
disabled={loading}
|
|
||||||
containerStyles={{ marginLeft: '1rem', maxWidth: '400px' }}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={!loading && !data}
|
|
||||||
show={() => (
|
|
||||||
<TablePlaceholder>
|
|
||||||
{data === undefined
|
|
||||||
? 'None of the feature toggles were evaluated yet.'
|
|
||||||
: 'No results found.'}
|
|
||||||
</TablePlaceholder>
|
|
||||||
)}
|
|
||||||
elseShow={() => (
|
|
||||||
<Box ref={ref}>
|
|
||||||
<SearchHighlightProvider
|
|
||||||
value={getSearchText(searchValue)}
|
|
||||||
>
|
|
||||||
<VirtualizedTable
|
|
||||||
rows={rows}
|
|
||||||
headerGroups={headerGroups}
|
|
||||||
prepareRow={prepareRow}
|
|
||||||
/>
|
|
||||||
</SearchHighlightProvider>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={
|
|
||||||
data.length === 0 && searchValue?.length > 0
|
|
||||||
}
|
|
||||||
show={
|
|
||||||
<TablePlaceholder>
|
|
||||||
No feature toggles found matching “
|
|
||||||
{searchValue}”
|
|
||||||
</TablePlaceholder>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={
|
|
||||||
data && data.length === 0 && !searchValue
|
|
||||||
}
|
|
||||||
show={
|
|
||||||
<TablePlaceholder>
|
|
||||||
No features toggles to display
|
|
||||||
</TablePlaceholder>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -13,21 +13,6 @@ export const usePlaygroundApi = () => {
|
|||||||
|
|
||||||
const URI = 'api/admin/playground';
|
const URI = 'api/admin/playground';
|
||||||
|
|
||||||
const evaluatePlayground = async (payload: PlaygroundRequestSchema) => {
|
|
||||||
const req = createRequest(URI, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await makeRequest(req.caller, req.id);
|
|
||||||
|
|
||||||
return res.json() as Promise<PlaygroundResponseSchema>;
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const evaluateAdvancedPlayground = async (
|
const evaluateAdvancedPlayground = async (
|
||||||
payload: AdvancedPlaygroundRequestSchema
|
payload: AdvancedPlaygroundRequestSchema
|
||||||
) => {
|
) => {
|
||||||
@ -47,7 +32,6 @@ export const usePlaygroundApi = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
evaluatePlayground,
|
|
||||||
evaluateAdvancedPlayground,
|
evaluateAdvancedPlayground,
|
||||||
errors,
|
errors,
|
||||||
loading,
|
loading,
|
||||||
|
@ -67,7 +67,6 @@ exports[`should create default config 1`] = `
|
|||||||
"isEnabled": [Function],
|
"isEnabled": [Function],
|
||||||
},
|
},
|
||||||
"flags": {
|
"flags": {
|
||||||
"advancedPlayground": false,
|
|
||||||
"anonymiseEventLog": false,
|
"anonymiseEventLog": false,
|
||||||
"caseInsensitiveInOperators": false,
|
"caseInsensitiveInOperators": false,
|
||||||
"cleanClientApi": false,
|
"cleanClientApi": false,
|
||||||
@ -102,7 +101,6 @@ exports[`should create default config 1`] = `
|
|||||||
},
|
},
|
||||||
"flagResolver": FlagResolver {
|
"flagResolver": FlagResolver {
|
||||||
"experiments": {
|
"experiments": {
|
||||||
"advancedPlayground": false,
|
|
||||||
"anonymiseEventLog": false,
|
"anonymiseEventLog": false,
|
||||||
"caseInsensitiveInOperators": false,
|
"caseInsensitiveInOperators": false,
|
||||||
"cleanClientApi": false,
|
"cleanClientApi": false,
|
||||||
|
@ -55,7 +55,7 @@ export default class PlaygroundController extends Controller {
|
|||||||
},
|
},
|
||||||
requestBody: createRequestSchema('playgroundRequestSchema'),
|
requestBody: createRequestSchema('playgroundRequestSchema'),
|
||||||
description:
|
description:
|
||||||
'Use the provided `context`, `environment`, and `projects` to evaluate toggles on this Unleash instance. Returns a list of all toggles that match the parameters and what they evaluate to. The response also contains the input parameters that were provided.',
|
'Deprecated. Will be removed in the next Unleash major update. Use the provided `context`, `environment`, and `projects` to evaluate toggles on this Unleash instance. Returns a list of all toggles that match the parameters and what they evaluate to. The response also contains the input parameters that were provided.',
|
||||||
summary:
|
summary:
|
||||||
'Evaluate an Unleash context against a set of environments and projects.',
|
'Evaluate an Unleash context against a set of environments and projects.',
|
||||||
}),
|
}),
|
||||||
@ -115,27 +115,23 @@ export default class PlaygroundController extends Controller {
|
|||||||
req: Request<any, any, AdvancedPlaygroundRequestSchema>,
|
req: Request<any, any, AdvancedPlaygroundRequestSchema>,
|
||||||
res: Response<AdvancedPlaygroundResponseSchema>,
|
res: Response<AdvancedPlaygroundResponseSchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (this.flagResolver.isEnabled('advancedPlayground')) {
|
// used for runtime control, do not remove
|
||||||
const { payload } =
|
const { payload } = this.flagResolver.getVariant('advancedPlayground');
|
||||||
this.flagResolver.getVariant('advancedPlayground');
|
const limit =
|
||||||
const limit =
|
payload?.value && Number.isInteger(parseInt(payload?.value))
|
||||||
payload?.value && Number.isInteger(parseInt(payload?.value))
|
? parseInt(payload?.value)
|
||||||
? parseInt(payload?.value)
|
: 15000;
|
||||||
: 15000;
|
|
||||||
|
|
||||||
const result = await this.playgroundService.evaluateAdvancedQuery(
|
const result = await this.playgroundService.evaluateAdvancedQuery(
|
||||||
req.body.projects || '*',
|
req.body.projects || '*',
|
||||||
req.body.environments,
|
req.body.environments,
|
||||||
req.body.context,
|
req.body.context,
|
||||||
limit,
|
limit,
|
||||||
);
|
);
|
||||||
|
|
||||||
const response: AdvancedPlaygroundResponseSchema =
|
const response: AdvancedPlaygroundResponseSchema =
|
||||||
advancedPlaygroundViewModel(req.body, result);
|
advancedPlaygroundViewModel(req.body, result);
|
||||||
|
|
||||||
res.json(response);
|
res.json(response);
|
||||||
} else {
|
|
||||||
res.status(409).end();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,10 +95,6 @@ const flags: IFlags = {
|
|||||||
process.env.DISABLE_NOTIFICATIONS,
|
process.env.DISABLE_NOTIFICATIONS,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
advancedPlayground: parseEnvVarBoolean(
|
|
||||||
process.env.ADVANCED_PLAYGROUND,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
customRootRoles: parseEnvVarBoolean(
|
customRootRoles: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_EXPERIMENTAL_CUSTOM_ROOT_ROLES,
|
process.env.UNLEASH_EXPERIMENTAL_CUSTOM_ROOT_ROLES,
|
||||||
false,
|
false,
|
||||||
|
@ -37,7 +37,6 @@ process.nextTick(async () => {
|
|||||||
embedProxyFrontend: true,
|
embedProxyFrontend: true,
|
||||||
anonymiseEventLog: false,
|
anonymiseEventLog: false,
|
||||||
responseTimeWithAppNameKillSwitch: false,
|
responseTimeWithAppNameKillSwitch: false,
|
||||||
advancedPlayground: true,
|
|
||||||
strategyVariant: true,
|
strategyVariant: true,
|
||||||
newProjectLayout: true,
|
newProjectLayout: true,
|
||||||
emitPotentiallyStaleEvents: true,
|
emitPotentiallyStaleEvents: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user