1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

refactor: clean up strategy parameter types (#944)

* refactor: fix splash page button background color

* refactor: regenerate OpenAPI client

* refactor: clean up strategy parameter types

* refactor: remove index signature from IConstraint

* refactor: fix never-seen status in features list
This commit is contained in:
olav 2022-05-04 15:16:34 +02:00 committed by GitHub
parent 7b60ef2aa6
commit 35262e404b
42 changed files with 1155 additions and 241 deletions

View File

@ -118,7 +118,7 @@ describe('feature', () => {
expect(req.body.name).to.equal('flexibleRollout');
expect(req.body.parameters.groupId).to.equal(featureToggleName);
expect(req.body.parameters.stickiness).to.equal('default');
expect(req.body.parameters.rollout).to.equal(100);
expect(req.body.parameters.rollout).to.equal('100');
if (ENTERPRISE) {
expect(req.body.constraints.length).to.equal(1);
@ -159,7 +159,7 @@ describe('feature', () => {
req => {
expect(req.body.parameters.groupId).to.equal('new-group-id');
expect(req.body.parameters.stickiness).to.equal('sessionId');
expect(req.body.parameters.rollout).to.equal(100);
expect(req.body.parameters.rollout).to.equal('100');
if (ENTERPRISE) {
expect(req.body.constraints.length).to.equal(1);

View File

@ -4,6 +4,7 @@ import Input from 'component/common/Input/Input';
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
import React, { useState } from 'react';
import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader';
import { parseParameterStrings } from 'utils/parseParameter';
interface IFreeTextInputProps {
values: string[];
@ -74,17 +75,9 @@ export const FreeTextInput = ({
return;
}
setError('');
if (inputValues.includes(',')) {
const newValues = inputValues
.split(',')
.filter(values => values)
.map(value => value.trim());
setValues(uniqueValues([...values, ...newValues]));
} else {
setValues(uniqueValues([...values, inputValues.trim()]));
}
setValues(
uniqueValues([...values, ...parseParameterStrings(inputValues)])
);
setInputValues('');
};

View File

@ -8,7 +8,7 @@ import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFe
import { formatUnknownError } from 'utils/formatUnknownError';
import { useHistory } from 'react-router-dom';
import useToast from 'hooks/useToast';
import { IFeatureStrategy, IStrategyPayload } from 'interfaces/strategy';
import { IFeatureStrategy, IFeatureStrategyPayload } from 'interfaces/strategy';
import { UPDATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions';
import { ISegment } from 'interfaces/segment';
import { useSegmentsApi } from 'hooks/api/actions/useSegmentsApi/useSegmentsApi';
@ -122,7 +122,7 @@ export const FeatureStrategyEdit = () => {
export const createStrategyPayload = (
strategy: Partial<IFeatureStrategy>
): IStrategyPayload => {
): IFeatureStrategyPayload => {
return {
name: strategy.name,
constraints: strategy.constraints ?? [],

View File

@ -28,7 +28,7 @@ export const FeatureStrategyType = ({
return definition.name === strategy.name;
});
const updateParameter = (field: string, value: unknown) => {
const updateParameter = (field: string, value: string) => {
setStrategy(
produce(draft => {
draft.parameters = draft.parameters ?? {};
@ -48,7 +48,7 @@ export const FeatureStrategyType = ({
return (
<FlexibleStrategy
context={context}
parameters={strategy.parameters ?? []}
parameters={strategy.parameters ?? {}}
updateParameter={updateParameter}
editable={hasAccess}
/>
@ -56,7 +56,7 @@ export const FeatureStrategyType = ({
case 'userWithId':
return (
<UserWithIdStrategy
parameters={strategy.parameters ?? []}
parameters={strategy.parameters ?? {}}
updateParameter={updateParameter}
editable={hasAccess}
/>
@ -65,7 +65,7 @@ export const FeatureStrategyType = ({
return (
<GeneralStrategy
strategyDefinition={strategyDefinition}
parameters={strategy.parameters ?? []}
parameters={strategy.parameters ?? {}}
updateParameter={updateParameter}
editable={hasAccess}
/>

View File

@ -72,14 +72,9 @@ export const FeatureToggleListItem = memo<IFeatureToggleListItemProps>(
className={classnames(styles.listItem, className)}
>
<span className={styles.listItemMetric}>
<ConditionallyRender
condition={Boolean(lastSeenAt)}
show={() => (
<FeatureStatus
lastSeenAt={lastSeenAt as Date}
tooltipPlacement="left"
/>
)}
<FeatureStatus
lastSeenAt={lastSeenAt}
tooltipPlacement="left"
/>
</span>
<span

View File

@ -1,5 +1,9 @@
import { Fragment } from 'react';
import { IConstraint, IFeatureStrategy, IParameter } from 'interfaces/strategy';
import {
IFeatureStrategy,
IFeatureStrategyParameters,
IConstraint,
} from 'interfaces/strategy';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle';
import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator';
@ -10,9 +14,14 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { FeatureOverviewSegment } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSegment/FeatureOverviewSegment';
import { ConstraintAccordionList } from 'component/common/ConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList';
import { useStyles } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewExecution/FeatureOverviewExecution.styles';
import {
parseParameterString,
parseParameterNumber,
parseParameterStrings,
} from 'utils/parseParameter';
interface IFeatureOverviewExecutionProps {
parameters: IParameter;
parameters: IFeatureStrategyParameters;
constraints?: IConstraint[];
strategy: IFeatureStrategy;
percentageFill?: string;
@ -52,15 +61,16 @@ const FeatureOverviewExecution = ({
is included.
</p>
<PercentageCircle percentage={parameters[key]} />
<PercentageCircle
percentage={parseParameterNumber(
parameters[key]
)}
/>
</Fragment>
);
case 'userIds':
case 'UserIds':
const users = parameters[key]
.split(',')
.filter((userId: string) => userId);
const users = parseParameterStrings(parameters[key]);
return (
<FeatureOverviewExecutionChips
key={key}
@ -70,10 +80,7 @@ const FeatureOverviewExecution = ({
);
case 'hostNames':
case 'HostNames':
const hosts = parameters[key]
.split(',')
.filter((hosts: string) => hosts);
const hosts = parseParameterStrings(parameters[key]);
return (
<FeatureOverviewExecutionChips
key={key}
@ -82,10 +89,7 @@ const FeatureOverviewExecution = ({
/>
);
case 'IPs':
const IPs = parameters[key]
.split(',')
.filter((hosts: string) => hosts);
const IPs = parseParameterStrings(parameters[key]);
return (
<FeatureOverviewExecutionChips
key={key}
@ -108,10 +112,9 @@ const FeatureOverviewExecution = ({
const notLastItem = index !== definition?.parameters?.length - 1;
switch (param?.type) {
case 'list':
const values = strategy?.parameters[param.name]
.split(',')
.filter((val: string) => val);
const values = parseParameterStrings(
strategy?.parameters[param.name]
);
return (
<Fragment key={param?.name}>
<FeatureOverviewExecutionChips
@ -134,9 +137,10 @@ const FeatureOverviewExecution = ({
: ''}{' '}
is included.
</p>
<PercentageCircle
percentage={strategy.parameters[param.name]}
percentage={parseParameterNumber(
strategy.parameters[param.name]
)}
/>
<ConditionallyRender
condition={notLastItem}
@ -156,7 +160,10 @@ const FeatureOverviewExecution = ({
{strategy.parameters[param.name]}
</p>
<ConditionallyRender
condition={strategy.parameters[param.name]}
condition={
typeof strategy.parameters[param.name] !==
'undefined'
}
show={
<ConditionallyRender
condition={notLastItem}
@ -167,10 +174,15 @@ const FeatureOverviewExecution = ({
</Fragment>
);
case 'string':
const value = strategy.parameters[param.name];
const value = parseParameterString(
strategy.parameters[param.name]
);
return (
<ConditionallyRender
condition={value !== undefined}
condition={
typeof strategy.parameters[param.name] !==
'undefined'
}
key={param.name}
show={
<>
@ -198,7 +210,9 @@ const FeatureOverviewExecution = ({
/>
);
case 'number':
const number = strategy.parameters[param.name];
const number = parseParameterNumber(
strategy.parameters[param.name]
);
return (
<ConditionallyRender
condition={number !== undefined}

View File

@ -47,7 +47,7 @@ function getColor(unit?: string): string {
}
interface IFeatureStatusProps {
lastSeenAt?: string | Date;
lastSeenAt?: string | Date | null;
tooltipPlacement?: TooltipProps['placement'];
}
@ -75,7 +75,7 @@ const FeatureStatus = ({
return (
<ConditionallyRender
condition={Boolean(lastSeenAt)}
show={
show={() => (
<TimeAgo
date={lastSeenAt!}
title=""
@ -98,7 +98,7 @@ const FeatureStatus = ({
);
}}
/>
}
)}
elseShow={
<Wrapper
toolTip="No usage reported from connected applications"

View File

@ -1,5 +1,5 @@
import { Typography } from '@mui/material';
import { IParameter } from 'interfaces/strategy';
import { IFeatureStrategyParameters } from 'interfaces/strategy';
import RolloutSlider from '../RolloutSlider/RolloutSlider';
import Select from 'component/common/select';
import React from 'react';
@ -9,6 +9,10 @@ import {
FLEXIBLE_STRATEGY_STICKINESS_ID,
} from 'utils/testIds';
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
import {
parseParameterNumber,
parseParameterString,
} from 'utils/parseParameter';
const builtInStickinessOptions = [
{ key: 'default', label: 'default' },
@ -18,8 +22,8 @@ const builtInStickinessOptions = [
];
interface IFlexibleStrategyProps {
parameters: IParameter;
updateParameter: (field: string, value: any) => void;
parameters: IFeatureStrategyParameters;
updateParameter: (field: string, value: string) => void;
context: any;
editable: boolean;
}
@ -54,15 +58,15 @@ const FlexibleStrategy = ({
const stickinessOptions = resolveStickiness();
const rollout =
parameters.rollout !== undefined ? parameters.rollout : '100';
const stickiness = parameters.stickiness;
const groupId = parameters.groupId;
parameters.rollout !== undefined
? parseParameterNumber(parameters.rollout)
: 100;
return (
<div>
<RolloutSlider
name="Rollout"
value={parseInt(rollout)}
value={rollout}
disabled={!editable}
onChange={updateRollout}
/>
@ -86,7 +90,7 @@ const FlexibleStrategy = ({
name="stickiness"
label="Stickiness"
options={stickinessOptions}
value={stickiness}
value={parseParameterString(parameters.stickiness)}
disabled={!editable}
data-testid={FLEXIBLE_STRATEGY_STICKINESS_ID}
onChange={e => onUpdate('stickiness')(e.target.value)}
@ -109,7 +113,7 @@ const FlexibleStrategy = ({
<Input
label="groupId"
id="groupId-input"
value={groupId || ''}
value={parseParameterString(parameters.groupId)}
disabled={!editable}
onChange={e => onUpdate('groupId')(e.target.value)}
data-testid={FLEXIBLE_STRATEGY_GROUP_ID}

View File

@ -2,13 +2,18 @@ import React from 'react';
import { FormControlLabel, Switch, TextField, Tooltip } from '@mui/material';
import StrategyInputList from '../StrategyInputList/StrategyInputList';
import RolloutSlider from '../RolloutSlider/RolloutSlider';
import { IParameter, IStrategy } from 'interfaces/strategy';
import { IStrategy, IFeatureStrategyParameters } from 'interfaces/strategy';
import { useStyles } from './GeneralStrategy.styles';
import {
parseParameterNumber,
parseParameterStrings,
parseParameterString,
} from 'utils/parseParameter';
interface IGeneralStrategyProps {
parameters: IParameter;
parameters: IFeatureStrategyParameters;
strategyDefinition: IStrategy;
updateParameter: (field: string, value: any) => void;
updateParameter: (field: string, value: string) => void;
editable: boolean;
}
@ -29,9 +34,13 @@ const GeneralStrategy = ({
updateParameter(field, value);
};
const onChangePercentage = (field: string, evt: Event, newValue: any) => {
const onChangePercentage = (
field: string,
evt: Event,
newValue: number | number[]
) => {
evt.preventDefault();
updateParameter(field, newValue);
updateParameter(field, newValue.toString());
};
const handleSwitchChange = (field: string, currentValue: any) => {
@ -47,15 +56,8 @@ const GeneralStrategy = ({
<>
{strategyDefinition.parameters.map(
({ name, type, description, required }) => {
let value = parameters[name];
if (type === 'percentage') {
if (
value == null ||
(typeof value === 'string' && value === '')
) {
value = 0;
}
const value = parseParameterNumber(parameters[name]);
return (
<div key={name}>
<br />
@ -66,7 +68,7 @@ const GeneralStrategy = ({
name
)}
disabled={!editable}
value={1 * value}
value={value}
minLabel="off"
maxLabel="on"
/>
@ -78,15 +80,12 @@ const GeneralStrategy = ({
</div>
);
} else if (type === 'list') {
let list: string[] = [];
if (typeof value === 'string') {
list = value.trim().split(',').filter(Boolean);
}
const values = parseParameterStrings(parameters[name]);
return (
<div key={name}>
<StrategyInputList
name={name}
list={list}
list={values}
disabled={!editable}
setConfig={updateParameter}
/>
@ -99,8 +98,9 @@ const GeneralStrategy = ({
);
} else if (type === 'number') {
const regex = new RegExp('^\\d+$');
const value = parseParameterString(parameters[name]);
const error =
value?.length > 0 ? !regex.test(value) : false;
value.length > 0 ? !regex.test(value) : false;
return (
<div key={name} className={styles.generalSection}>
@ -130,6 +130,7 @@ const GeneralStrategy = ({
</div>
);
} else if (type === 'boolean') {
const value = parseParameterString(parameters[name]);
return (
<div key={name} style={{ padding: '20px 0' }}>
<Tooltip
@ -154,6 +155,7 @@ const GeneralStrategy = ({
</div>
);
} else {
const value = parseParameterString(parameters[name]);
return (
<div key={name} className={styles.generalSection}>
<TextField

View File

@ -1,8 +1,9 @@
import { IParameter } from 'interfaces/strategy';
import { IFeatureStrategyParameters } from 'interfaces/strategy';
import StrategyInputList from '../StrategyInputList/StrategyInputList';
import { parseParameterStrings } from 'utils/parseParameter';
interface IUserWithIdStrategyProps {
parameters: IParameter;
parameters: IFeatureStrategyParameters;
updateParameter: (field: string, value: string) => void;
editable: boolean;
}
@ -12,18 +13,11 @@ const UserWithIdStrategy = ({
parameters,
updateParameter,
}: IUserWithIdStrategyProps) => {
const value = parameters.userIds;
let list: string[] = [];
if (typeof value === 'string') {
list = value.trim().split(',').filter(Boolean);
}
return (
<div>
<StrategyInputList
name="userIds"
list={list}
list={parseParameterStrings(parameters.userIds)}
disabled={!editable}
setConfig={updateParameter}
/>

View File

@ -72,7 +72,7 @@ export const useStyles = makeStyles()(theme => ({
color: 'inherit',
},
button: {
background: 'white',
background: 'white !important',
color: theme.palette.primary.main,
},
}));

View File

@ -36,7 +36,7 @@ import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
import useStrategiesApi from 'hooks/api/actions/useStrategiesApi/useStrategiesApi';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { ICustomStrategy } from 'interfaces/strategy';
import { IStrategy } from 'interfaces/strategy';
interface IDialogueMetaData {
show: boolean;
@ -102,7 +102,7 @@ export const StrategiesList = () => {
</Link>
);
const onReactivateStrategy = (strategy: ICustomStrategy) => {
const onReactivateStrategy = (strategy: IStrategy) => {
setDialogueMetaData({
show: true,
title: 'Really reactivate strategy?',
@ -122,7 +122,7 @@ export const StrategiesList = () => {
});
};
const onDeprecateStrategy = (strategy: ICustomStrategy) => {
const onDeprecateStrategy = (strategy: IStrategy) => {
setDialogueMetaData({
show: true,
title: 'Really deprecate strategy?',
@ -142,7 +142,7 @@ export const StrategiesList = () => {
});
};
const onDeleteStrategy = (strategy: ICustomStrategy) => {
const onDeleteStrategy = (strategy: IStrategy) => {
setDialogueMetaData({
show: true,
title: 'Really delete strategy?',
@ -162,7 +162,7 @@ export const StrategiesList = () => {
});
};
const reactivateButton = (strategy: ICustomStrategy) => (
const reactivateButton = (strategy: IStrategy) => (
<PermissionIconButton
onClick={() => onReactivateStrategy(strategy)}
permission={UPDATE_STRATEGY}
@ -172,7 +172,7 @@ export const StrategiesList = () => {
</PermissionIconButton>
);
const deprecateButton = (strategy: ICustomStrategy) => (
const deprecateButton = (strategy: IStrategy) => (
<ConditionallyRender
condition={strategy.name === 'default'}
show={
@ -198,7 +198,7 @@ export const StrategiesList = () => {
/>
);
const editButton = (strategy: ICustomStrategy) => (
const editButton = (strategy: IStrategy) => (
<ConditionallyRender
condition={strategy?.editable}
show={
@ -224,7 +224,7 @@ export const StrategiesList = () => {
/>
);
const deleteButton = (strategy: ICustomStrategy) => (
const deleteButton = (strategy: IStrategy) => (
<ConditionallyRender
condition={strategy?.editable}
show={

View File

@ -4,16 +4,16 @@ import { useStyles } from './StrategyForm.styles';
import { Add } from '@mui/icons-material';
import { trim } from 'component/common/util';
import { StrategyParameters } from './StrategyParameters/StrategyParameters';
import { ICustomStrategyParameter } from 'interfaces/strategy';
import { IStrategyParameter } from 'interfaces/strategy';
import React from 'react';
interface IStrategyFormProps {
strategyName: string;
strategyDesc: string;
params: ICustomStrategyParameter[];
params: IStrategyParameter[];
setStrategyName: React.Dispatch<React.SetStateAction<string>>;
setStrategyDesc: React.Dispatch<React.SetStateAction<string>>;
setParams: React.Dispatch<React.SetStateAction<ICustomStrategyParameter[]>>;
setParams: React.Dispatch<React.SetStateAction<IStrategyParameter[]>>;
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
handleCancel: () => void;
errors: { [key: string]: string };

View File

@ -5,7 +5,7 @@ import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
import Input from 'component/common/Input/Input';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import React from 'react';
import { ICustomStrategyParameter } from 'interfaces/strategy';
import { IStrategyParameter } from 'interfaces/strategy';
const paramTypesOptions = [
{
@ -41,10 +41,10 @@ const paramTypesOptions = [
interface IStrategyParameterProps {
set: React.Dispatch<React.SetStateAction<object>>;
input: ICustomStrategyParameter;
input: IStrategyParameter;
index: number;
params: ICustomStrategyParameter[];
setParams: React.Dispatch<React.SetStateAction<ICustomStrategyParameter[]>>;
params: IStrategyParameter[];
setParams: React.Dispatch<React.SetStateAction<IStrategyParameter[]>>;
errors: { [key: string]: string };
}

View File

@ -1,11 +1,11 @@
import { StrategyParameter } from './StrategyParameter/StrategyParameter';
import React from 'react';
import { ICustomStrategyParameter } from 'interfaces/strategy';
import { IStrategyParameter } from 'interfaces/strategy';
interface IStrategyParametersProps {
input: ICustomStrategyParameter[];
input: IStrategyParameter[];
updateParameter: (index: number, updated: object) => void;
setParams: React.Dispatch<React.SetStateAction<ICustomStrategyParameter[]>>;
setParams: React.Dispatch<React.SetStateAction<IStrategyParameter[]>>;
errors: { [key: string]: string };
}

View File

@ -11,7 +11,7 @@ import { AppsLinkList } from 'component/common';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import styles from '../../strategies.module.scss';
import { TogglesLinkList } from 'component/strategies/TogglesLinkList/TogglesLinkList';
import { IParameter, IStrategy } from 'interfaces/strategy';
import { IStrategy, IStrategyParameter } from 'interfaces/strategy';
import { IApplication } from 'interfaces/application';
import { FeatureSchema } from 'openapi';
@ -27,7 +27,7 @@ export const StrategyDetails = ({
toggles,
}: IStrategyDetailsProps) => {
const { parameters = [] } = strategy;
const renderParameters = (params: IParameter[]) => {
const renderParameters = (params: IStrategyParameter[]) => {
if (params.length > 0) {
return params.map(({ name, type, description, required }, i) => (
<ListItem key={`${name}-${i}`}>

View File

@ -1,11 +1,11 @@
import { ICustomStrategyParameter } from 'interfaces/strategy';
import { IStrategyParameter } from 'interfaces/strategy';
import { useEffect, useState } from 'react';
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
export const useStrategyForm = (
initialStrategyName: string = '',
initialStrategyDesc: string = '',
initialParams: ICustomStrategyParameter[] = []
initialParams: IStrategyParameter[] = []
) => {
const [strategyName, setStrategyName] = useState(initialStrategyName);
const [strategyDesc, setStrategyDesc] = useState(initialStrategyDesc);

View File

@ -1,9 +1,9 @@
import { ITag } from 'interfaces/tags';
import useAPI from '../useApi/useApi';
import { Operation } from 'fast-json-patch';
import { IConstraint } from 'interfaces/strategy';
import { CreateFeatureSchema } from 'openapi';
import { openApiAdmin } from 'utils/openapiClient';
import { IConstraint } from 'interfaces/strategy';
const useFeatureApi = () => {
const { makeRequest, createRequest, errors, loading } = useAPI({

View File

@ -1,4 +1,4 @@
import { IStrategyPayload, IFeatureStrategy } from 'interfaces/strategy';
import { IFeatureStrategyPayload, IFeatureStrategy } from 'interfaces/strategy';
import useAPI from '../useApi/useApi';
const useFeatureStrategyApi = () => {
@ -10,7 +10,7 @@ const useFeatureStrategyApi = () => {
projectId: string,
featureId: string,
environmentId: string,
payload: IStrategyPayload
payload: IFeatureStrategyPayload
): Promise<IFeatureStrategy> => {
const path = `api/admin/projects/${projectId}/features/${featureId}/environments/${environmentId}/strategies`;
const req = createRequest(
@ -41,7 +41,7 @@ const useFeatureStrategyApi = () => {
featureId: string,
environmentId: string,
strategyId: string,
payload: IStrategyPayload
payload: IFeatureStrategyPayload
): Promise<void> => {
const path = `api/admin/projects/${projectId}/features/${featureId}/environments/${environmentId}/strategies/${strategyId}`;
const req = createRequest(

View File

@ -1,4 +1,4 @@
import { ICustomStrategyPayload } from 'interfaces/strategy';
import { IStrategyPayload } from 'interfaces/strategy';
import useAPI from '../useApi/useApi';
const useStrategiesApi = () => {
@ -7,7 +7,7 @@ const useStrategiesApi = () => {
});
const URI = 'api/admin/strategies';
const createStrategy = async (strategy: ICustomStrategyPayload) => {
const createStrategy = async (strategy: IStrategyPayload) => {
const req = createRequest(URI, {
method: 'POST',
body: JSON.stringify(strategy),
@ -22,7 +22,7 @@ const useStrategiesApi = () => {
}
};
const updateStrategy = async (strategy: ICustomStrategyPayload) => {
const updateStrategy = async (strategy: IStrategyPayload) => {
const path = `${URI}/${strategy.name}`;
const req = createRequest(path, {
method: 'PUT',
@ -38,7 +38,7 @@ const useStrategiesApi = () => {
}
};
const removeStrategy = async (strategy: ICustomStrategyPayload) => {
const removeStrategy = async (strategy: IStrategyPayload) => {
const path = `${URI}/${strategy.name}`;
const req = createRequest(path, { method: 'DELETE' });
@ -51,7 +51,7 @@ const useStrategiesApi = () => {
}
};
const deprecateStrategy = async (strategy: ICustomStrategyPayload) => {
const deprecateStrategy = async (strategy: IStrategyPayload) => {
const path = `${URI}/${strategy.name}/deprecate`;
const req = createRequest(path, {
method: 'POST',
@ -66,7 +66,7 @@ const useStrategiesApi = () => {
}
};
const reactivateStrategy = async (strategy: ICustomStrategyPayload) => {
const reactivateStrategy = async (strategy: IStrategyPayload) => {
const path = `${URI}/${strategy.name}/reactivate`;
const req = createRequest(path, { method: 'POST' });

View File

@ -1,8 +1,8 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { useFeaturesFilter } from 'hooks/useFeaturesFilter';
import { IConstraint, IFeatureStrategy } from 'interfaces/strategy';
import { FeatureSchema } from 'openapi';
import { FeatureSchema, FeatureSchemaStrategies } from 'openapi';
import parseISO from 'date-fns/parseISO';
import { IConstraint } from 'interfaces/strategy';
test('useFeaturesFilter empty', () => {
const { result } = renderHook(() => useFeaturesFilter([]));
@ -108,8 +108,8 @@ const mockFeatureToggle = (
};
const mockFeatureStrategy = (
overrides?: Partial<IFeatureStrategy>
): IFeatureStrategy => {
overrides?: Partial<FeatureSchemaStrategies>
): FeatureSchemaStrategies => {
return {
id: '1',
name: '1',

View File

@ -5,19 +5,42 @@ export interface IFeatureStrategy {
strategyName?: string;
name: string;
constraints: IConstraint[];
parameters: IParameter;
parameters: IFeatureStrategyParameters;
featureName?: string;
projectId?: string;
environment?: string;
}
export interface IFeatureStrategyParameters {
[key: string]: string | number | undefined;
}
export interface IFeatureStrategyPayload {
name?: string;
constraints: IConstraint[];
parameters: IFeatureStrategyParameters;
}
export interface IStrategy {
name: string;
displayName: string;
editable: boolean;
deprecated: boolean;
description: string;
parameters: IParameter[];
parameters: IStrategyParameter[];
}
export interface IStrategyParameter {
name: string;
description: string;
required: boolean;
type: string;
}
export interface IStrategyPayload {
name: string;
description: string;
parameters: IStrategyParameter[];
}
export interface IConstraint {
@ -27,38 +50,4 @@ export interface IConstraint {
caseInsensitive?: boolean;
operator: Operator;
contextName: string;
[index: string]: unknown;
}
export interface IParameter {
groupId?: string;
rollout?: string;
stickiness?: string;
[index: string]: any;
}
export interface IStrategyPayload {
name?: string;
constraints: IConstraint[];
parameters: IParameter;
}
export interface ICustomStrategyParameter {
name: string;
description: string;
required: boolean;
type: string;
}
export interface ICustomStrategyPayload {
name: string;
description: string;
parameters: IParameter[];
}
export interface ICustomStrategy {
name: string;
description: string;
parameters: IParameter[];
editable: boolean;
}

View File

@ -15,21 +15,51 @@
import * as runtime from '../runtime';
import {
ChangeProjectSchema,
ChangeProjectSchemaFromJSON,
ChangeProjectSchemaToJSON,
CreateFeatureSchema,
CreateFeatureSchemaFromJSON,
CreateFeatureSchemaToJSON,
CreateStrategySchema,
CreateStrategySchemaFromJSON,
CreateStrategySchemaToJSON,
FeatureSchema,
FeatureSchemaFromJSON,
FeatureSchemaToJSON,
FeaturesSchema,
FeaturesSchemaFromJSON,
FeaturesSchemaToJSON,
StrategySchema,
StrategySchemaFromJSON,
StrategySchemaToJSON,
} from '../models';
export interface ApiAdminArchiveFeaturesProjectIdGetRequest {
projectId: string;
}
export interface ApiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPostRequest {
projectId: string;
featureName: string;
changeProjectSchema: ChangeProjectSchema;
}
export interface ApiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesPostRequest {
projectId: string;
featureName: string;
environment: string;
createStrategySchema: CreateStrategySchema;
}
export interface ApiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesStrategyIdPutRequest {
projectId: string;
featureName: string;
environment: string;
strategyId: string;
createStrategySchema: CreateStrategySchema;
}
export interface ApiAdminProjectsProjectIdFeaturesFeatureNameGetRequest {
projectId: string;
featureName: string;
@ -137,6 +167,146 @@ export class AdminApi extends runtime.BaseAPI {
return await response.value();
}
/**
*/
async apiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPostRaw(requestParameters: ApiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPostRequest, initOverrides?: RequestInit): Promise<runtime.ApiResponse<void>> {
if (requestParameters.projectId === null || requestParameters.projectId === undefined) {
throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling apiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPost.');
}
if (requestParameters.featureName === null || requestParameters.featureName === undefined) {
throw new runtime.RequiredError('featureName','Required parameter requestParameters.featureName was null or undefined when calling apiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPost.');
}
if (requestParameters.changeProjectSchema === null || requestParameters.changeProjectSchema === undefined) {
throw new runtime.RequiredError('changeProjectSchema','Required parameter requestParameters.changeProjectSchema was null or undefined when calling apiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPost.');
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = this.configuration.apiKey("Authorization"); // apiKey authentication
}
const response = await this.request({
path: `/api/admin/projects/{projectId}/features/{featureName}/changeProject`.replace(`{${"projectId"}}`, encodeURIComponent(String(requestParameters.projectId))).replace(`{${"featureName"}}`, encodeURIComponent(String(requestParameters.featureName))),
method: 'POST',
headers: headerParameters,
query: queryParameters,
body: ChangeProjectSchemaToJSON(requestParameters.changeProjectSchema),
}, initOverrides);
return new runtime.VoidApiResponse(response);
}
/**
*/
async apiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPost(requestParameters: ApiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPostRequest, initOverrides?: RequestInit): Promise<void> {
await this.apiAdminProjectsProjectIdFeaturesFeatureNameChangeProjectPostRaw(requestParameters, initOverrides);
}
/**
*/
async apiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesPostRaw(requestParameters: ApiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesPostRequest, initOverrides?: RequestInit): Promise<runtime.ApiResponse<StrategySchema>> {
if (requestParameters.projectId === null || requestParameters.projectId === undefined) {
throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling apiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesPost.');
}
if (requestParameters.featureName === null || requestParameters.featureName === undefined) {
throw new runtime.RequiredError('featureName','Required parameter requestParameters.featureName was null or undefined when calling apiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesPost.');
}
if (requestParameters.environment === null || requestParameters.environment === undefined) {
throw new runtime.RequiredError('environment','Required parameter requestParameters.environment was null or undefined when calling apiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesPost.');
}
if (requestParameters.createStrategySchema === null || requestParameters.createStrategySchema === undefined) {
throw new runtime.RequiredError('createStrategySchema','Required parameter requestParameters.createStrategySchema was null or undefined when calling apiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesPost.');
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = this.configuration.apiKey("Authorization"); // apiKey authentication
}
const response = await this.request({
path: `/api/admin/projects/{projectId}/features/{featureName}/environments/{environment}/strategies`.replace(`{${"projectId"}}`, encodeURIComponent(String(requestParameters.projectId))).replace(`{${"featureName"}}`, encodeURIComponent(String(requestParameters.featureName))).replace(`{${"environment"}}`, encodeURIComponent(String(requestParameters.environment))),
method: 'POST',
headers: headerParameters,
query: queryParameters,
body: CreateStrategySchemaToJSON(requestParameters.createStrategySchema),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => StrategySchemaFromJSON(jsonValue));
}
/**
*/
async apiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesPost(requestParameters: ApiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesPostRequest, initOverrides?: RequestInit): Promise<StrategySchema> {
const response = await this.apiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesPostRaw(requestParameters, initOverrides);
return await response.value();
}
/**
*/
async apiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesStrategyIdPutRaw(requestParameters: ApiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesStrategyIdPutRequest, initOverrides?: RequestInit): Promise<runtime.ApiResponse<StrategySchema>> {
if (requestParameters.projectId === null || requestParameters.projectId === undefined) {
throw new runtime.RequiredError('projectId','Required parameter requestParameters.projectId was null or undefined when calling apiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesStrategyIdPut.');
}
if (requestParameters.featureName === null || requestParameters.featureName === undefined) {
throw new runtime.RequiredError('featureName','Required parameter requestParameters.featureName was null or undefined when calling apiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesStrategyIdPut.');
}
if (requestParameters.environment === null || requestParameters.environment === undefined) {
throw new runtime.RequiredError('environment','Required parameter requestParameters.environment was null or undefined when calling apiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesStrategyIdPut.');
}
if (requestParameters.strategyId === null || requestParameters.strategyId === undefined) {
throw new runtime.RequiredError('strategyId','Required parameter requestParameters.strategyId was null or undefined when calling apiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesStrategyIdPut.');
}
if (requestParameters.createStrategySchema === null || requestParameters.createStrategySchema === undefined) {
throw new runtime.RequiredError('createStrategySchema','Required parameter requestParameters.createStrategySchema was null or undefined when calling apiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesStrategyIdPut.');
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = this.configuration.apiKey("Authorization"); // apiKey authentication
}
const response = await this.request({
path: `/api/admin/projects/{projectId}/features/{featureName}/environments/{environment}/strategies/{strategyId}`.replace(`{${"projectId"}}`, encodeURIComponent(String(requestParameters.projectId))).replace(`{${"featureName"}}`, encodeURIComponent(String(requestParameters.featureName))).replace(`{${"environment"}}`, encodeURIComponent(String(requestParameters.environment))).replace(`{${"strategyId"}}`, encodeURIComponent(String(requestParameters.strategyId))),
method: 'PUT',
headers: headerParameters,
query: queryParameters,
body: CreateStrategySchemaToJSON(requestParameters.createStrategySchema),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => StrategySchemaFromJSON(jsonValue));
}
/**
*/
async apiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesStrategyIdPut(requestParameters: ApiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesStrategyIdPutRequest, initOverrides?: RequestInit): Promise<StrategySchema> {
const response = await this.apiAdminProjectsProjectIdFeaturesFeatureNameEnvironmentsEnvironmentStrategiesStrategyIdPutRaw(requestParameters, initOverrides);
return await response.value();
}
/**
*/
async apiAdminProjectsProjectIdFeaturesFeatureNameGetRaw(requestParameters: ApiAdminProjectsProjectIdFeaturesFeatureNameGetRequest, initOverrides?: RequestInit): Promise<runtime.ApiResponse<FeatureSchema>> {

View File

@ -0,0 +1,56 @@
/* tslint:disable */
/* eslint-disable */
/**
* Unleash API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 4.10.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
/**
*
* @export
* @interface ChangeProjectSchema
*/
export interface ChangeProjectSchema {
/**
*
* @type {string}
* @memberof ChangeProjectSchema
*/
newProjectId: string;
}
export function ChangeProjectSchemaFromJSON(json: any): ChangeProjectSchema {
return ChangeProjectSchemaFromJSONTyped(json, false);
}
export function ChangeProjectSchemaFromJSONTyped(json: any, ignoreDiscriminator: boolean): ChangeProjectSchema {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'newProjectId': json['newProjectId'],
};
}
export function ChangeProjectSchemaToJSON(value?: ChangeProjectSchema | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'newProjectId': value.newProjectId,
};
}

View File

@ -31,12 +31,30 @@ export interface ConstraintSchema {
* @memberof ConstraintSchema
*/
operator: string;
/**
*
* @type {boolean}
* @memberof ConstraintSchema
*/
caseInsensitive?: boolean;
/**
*
* @type {boolean}
* @memberof ConstraintSchema
*/
inverted?: boolean;
/**
*
* @type {Array<string>}
* @memberof ConstraintSchema
*/
values?: Array<string>;
/**
*
* @type {string}
* @memberof ConstraintSchema
*/
value?: string;
}
export function ConstraintSchemaFromJSON(json: any): ConstraintSchema {
@ -51,7 +69,10 @@ export function ConstraintSchemaFromJSONTyped(json: any, ignoreDiscriminator: bo
'contextName': json['contextName'],
'operator': json['operator'],
'caseInsensitive': !exists(json, 'caseInsensitive') ? undefined : json['caseInsensitive'],
'inverted': !exists(json, 'inverted') ? undefined : json['inverted'],
'values': !exists(json, 'values') ? undefined : json['values'],
'value': !exists(json, 'value') ? undefined : json['value'],
};
}
@ -66,7 +87,10 @@ export function ConstraintSchemaToJSON(value?: ConstraintSchema | null): any {
'contextName': value.contextName,
'operator': value.operator,
'caseInsensitive': value.caseInsensitive,
'inverted': value.inverted,
'values': value.values,
'value': value.value,
};
}

View File

@ -0,0 +1,87 @@
/* tslint:disable */
/* eslint-disable */
/**
* Unleash API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 4.10.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
import {
CreateStrategySchemaConstraints,
CreateStrategySchemaConstraintsFromJSON,
CreateStrategySchemaConstraintsFromJSONTyped,
CreateStrategySchemaConstraintsToJSON,
} from './CreateStrategySchemaConstraints';
/**
*
* @export
* @interface CreateStrategySchema
*/
export interface CreateStrategySchema {
/**
*
* @type {string}
* @memberof CreateStrategySchema
*/
name?: string;
/**
*
* @type {number}
* @memberof CreateStrategySchema
*/
sortOrder?: number;
/**
*
* @type {Array<CreateStrategySchemaConstraints>}
* @memberof CreateStrategySchema
*/
constraints?: Array<CreateStrategySchemaConstraints>;
/**
*
* @type {{ [key: string]: string; }}
* @memberof CreateStrategySchema
*/
parameters?: { [key: string]: string; };
}
export function CreateStrategySchemaFromJSON(json: any): CreateStrategySchema {
return CreateStrategySchemaFromJSONTyped(json, false);
}
export function CreateStrategySchemaFromJSONTyped(json: any, ignoreDiscriminator: boolean): CreateStrategySchema {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'name': !exists(json, 'name') ? undefined : json['name'],
'sortOrder': !exists(json, 'sortOrder') ? undefined : json['sortOrder'],
'constraints': !exists(json, 'constraints') ? undefined : ((json['constraints'] as Array<any>).map(CreateStrategySchemaConstraintsFromJSON)),
'parameters': !exists(json, 'parameters') ? undefined : json['parameters'],
};
}
export function CreateStrategySchemaToJSON(value?: CreateStrategySchema | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'name': value.name,
'sortOrder': value.sortOrder,
'constraints': value.constraints === undefined ? undefined : ((value.constraints as Array<any>).map(CreateStrategySchemaConstraintsToJSON)),
'parameters': value.parameters,
};
}

View File

@ -0,0 +1,96 @@
/* tslint:disable */
/* eslint-disable */
/**
* Unleash API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 4.10.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
/**
*
* @export
* @interface CreateStrategySchemaConstraints
*/
export interface CreateStrategySchemaConstraints {
/**
*
* @type {string}
* @memberof CreateStrategySchemaConstraints
*/
contextName: string;
/**
*
* @type {string}
* @memberof CreateStrategySchemaConstraints
*/
operator: string;
/**
*
* @type {boolean}
* @memberof CreateStrategySchemaConstraints
*/
caseInsensitive?: boolean;
/**
*
* @type {boolean}
* @memberof CreateStrategySchemaConstraints
*/
inverted?: boolean;
/**
*
* @type {Array<string>}
* @memberof CreateStrategySchemaConstraints
*/
values?: Array<string>;
/**
*
* @type {string}
* @memberof CreateStrategySchemaConstraints
*/
value?: string;
}
export function CreateStrategySchemaConstraintsFromJSON(json: any): CreateStrategySchemaConstraints {
return CreateStrategySchemaConstraintsFromJSONTyped(json, false);
}
export function CreateStrategySchemaConstraintsFromJSONTyped(json: any, ignoreDiscriminator: boolean): CreateStrategySchemaConstraints {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'contextName': json['contextName'],
'operator': json['operator'],
'caseInsensitive': !exists(json, 'caseInsensitive') ? undefined : json['caseInsensitive'],
'inverted': !exists(json, 'inverted') ? undefined : json['inverted'],
'values': !exists(json, 'values') ? undefined : json['values'],
'value': !exists(json, 'value') ? undefined : json['value'],
};
}
export function CreateStrategySchemaConstraintsToJSON(value?: CreateStrategySchemaConstraints | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'contextName': value.contextName,
'operator': value.operator,
'caseInsensitive': value.caseInsensitive,
'inverted': value.inverted,
'values': value.values,
'value': value.value,
};
}

View File

@ -14,17 +14,17 @@
import { exists, mapValues } from '../runtime';
import {
StrategySchema,
StrategySchemaFromJSON,
StrategySchemaFromJSONTyped,
StrategySchemaToJSON,
} from './StrategySchema';
FeatureSchemaStrategies,
FeatureSchemaStrategiesFromJSON,
FeatureSchemaStrategiesFromJSONTyped,
FeatureSchemaStrategiesToJSON,
} from './FeatureSchemaStrategies';
import {
VariantSchema,
VariantSchemaFromJSON,
VariantSchemaFromJSONTyped,
VariantSchemaToJSON,
} from './VariantSchema';
FeatureSchemaVariants,
FeatureSchemaVariantsFromJSON,
FeatureSchemaVariantsFromJSONTyped,
FeatureSchemaVariantsToJSON,
} from './FeatureSchemaVariants';
/**
*
@ -88,16 +88,16 @@ export interface FeatureSchema {
lastSeenAt?: Date | null;
/**
*
* @type {Array<StrategySchema>}
* @type {Array<FeatureSchemaStrategies>}
* @memberof FeatureSchema
*/
strategies?: Array<StrategySchema>;
strategies?: Array<FeatureSchemaStrategies>;
/**
*
* @type {Array<VariantSchema>}
* @type {Array<FeatureSchemaVariants>}
* @memberof FeatureSchema
*/
variants?: Array<VariantSchema>;
variants?: Array<FeatureSchemaVariants>;
}
export function FeatureSchemaFromJSON(json: any): FeatureSchema {
@ -119,8 +119,8 @@ export function FeatureSchemaFromJSONTyped(json: any, ignoreDiscriminator: boole
'impressionData': !exists(json, 'impressionData') ? undefined : json['impressionData'],
'createdAt': !exists(json, 'createdAt') ? undefined : (json['createdAt'] === null ? null : new Date(json['createdAt'])),
'lastSeenAt': !exists(json, 'lastSeenAt') ? undefined : (json['lastSeenAt'] === null ? null : new Date(json['lastSeenAt'])),
'strategies': !exists(json, 'strategies') ? undefined : ((json['strategies'] as Array<any>).map(StrategySchemaFromJSON)),
'variants': !exists(json, 'variants') ? undefined : ((json['variants'] as Array<any>).map(VariantSchemaFromJSON)),
'strategies': !exists(json, 'strategies') ? undefined : ((json['strategies'] as Array<any>).map(FeatureSchemaStrategiesFromJSON)),
'variants': !exists(json, 'variants') ? undefined : ((json['variants'] as Array<any>).map(FeatureSchemaVariantsFromJSON)),
};
}
@ -142,8 +142,8 @@ export function FeatureSchemaToJSON(value?: FeatureSchema | null): any {
'impressionData': value.impressionData,
'createdAt': value.createdAt === undefined ? undefined : (value.createdAt === null ? null : value.createdAt.toISOString().substr(0,10)),
'lastSeenAt': value.lastSeenAt === undefined ? undefined : (value.lastSeenAt === null ? null : value.lastSeenAt.toISOString().substr(0,10)),
'strategies': value.strategies === undefined ? undefined : ((value.strategies as Array<any>).map(StrategySchemaToJSON)),
'variants': value.variants === undefined ? undefined : ((value.variants as Array<any>).map(VariantSchemaToJSON)),
'strategies': value.strategies === undefined ? undefined : ((value.strategies as Array<any>).map(FeatureSchemaStrategiesToJSON)),
'variants': value.variants === undefined ? undefined : ((value.variants as Array<any>).map(FeatureSchemaVariantsToJSON)),
};
}

View File

@ -0,0 +1,64 @@
/* tslint:disable */
/* eslint-disable */
/**
* Unleash API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 4.10.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
/**
*
* @export
* @interface FeatureSchemaOverrides
*/
export interface FeatureSchemaOverrides {
/**
*
* @type {string}
* @memberof FeatureSchemaOverrides
*/
contextName: string;
/**
*
* @type {Array<string>}
* @memberof FeatureSchemaOverrides
*/
values: Array<string>;
}
export function FeatureSchemaOverridesFromJSON(json: any): FeatureSchemaOverrides {
return FeatureSchemaOverridesFromJSONTyped(json, false);
}
export function FeatureSchemaOverridesFromJSONTyped(json: any, ignoreDiscriminator: boolean): FeatureSchemaOverrides {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'contextName': json['contextName'],
'values': json['values'],
};
}
export function FeatureSchemaOverridesToJSON(value?: FeatureSchemaOverrides | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'contextName': value.contextName,
'values': value.values,
};
}

View File

@ -0,0 +1,87 @@
/* tslint:disable */
/* eslint-disable */
/**
* Unleash API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 4.10.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
import {
CreateStrategySchemaConstraints,
CreateStrategySchemaConstraintsFromJSON,
CreateStrategySchemaConstraintsFromJSONTyped,
CreateStrategySchemaConstraintsToJSON,
} from './CreateStrategySchemaConstraints';
/**
*
* @export
* @interface FeatureSchemaStrategies
*/
export interface FeatureSchemaStrategies {
/**
*
* @type {string}
* @memberof FeatureSchemaStrategies
*/
id: string;
/**
*
* @type {string}
* @memberof FeatureSchemaStrategies
*/
name: string;
/**
*
* @type {Array<CreateStrategySchemaConstraints>}
* @memberof FeatureSchemaStrategies
*/
constraints: Array<CreateStrategySchemaConstraints>;
/**
*
* @type {{ [key: string]: string; }}
* @memberof FeatureSchemaStrategies
*/
parameters: { [key: string]: string; };
}
export function FeatureSchemaStrategiesFromJSON(json: any): FeatureSchemaStrategies {
return FeatureSchemaStrategiesFromJSONTyped(json, false);
}
export function FeatureSchemaStrategiesFromJSONTyped(json: any, ignoreDiscriminator: boolean): FeatureSchemaStrategies {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'id': json['id'],
'name': json['name'],
'constraints': ((json['constraints'] as Array<any>).map(CreateStrategySchemaConstraintsFromJSON)),
'parameters': json['parameters'],
};
}
export function FeatureSchemaStrategiesToJSON(value?: FeatureSchemaStrategies | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'id': value.id,
'name': value.name,
'constraints': ((value.constraints as Array<any>).map(CreateStrategySchemaConstraintsToJSON)),
'parameters': value.parameters,
};
}

View File

@ -0,0 +1,103 @@
/* tslint:disable */
/* eslint-disable */
/**
* Unleash API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 4.10.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
import {
FeatureSchemaOverrides,
FeatureSchemaOverridesFromJSON,
FeatureSchemaOverridesFromJSONTyped,
FeatureSchemaOverridesToJSON,
} from './FeatureSchemaOverrides';
/**
*
* @export
* @interface FeatureSchemaVariants
*/
export interface FeatureSchemaVariants {
/**
*
* @type {string}
* @memberof FeatureSchemaVariants
*/
name: string;
/**
*
* @type {number}
* @memberof FeatureSchemaVariants
*/
weight: number;
/**
*
* @type {string}
* @memberof FeatureSchemaVariants
*/
weightType: string;
/**
*
* @type {string}
* @memberof FeatureSchemaVariants
*/
stickiness: string;
/**
*
* @type {object}
* @memberof FeatureSchemaVariants
*/
payload?: object;
/**
*
* @type {Array<FeatureSchemaOverrides>}
* @memberof FeatureSchemaVariants
*/
overrides?: Array<FeatureSchemaOverrides>;
}
export function FeatureSchemaVariantsFromJSON(json: any): FeatureSchemaVariants {
return FeatureSchemaVariantsFromJSONTyped(json, false);
}
export function FeatureSchemaVariantsFromJSONTyped(json: any, ignoreDiscriminator: boolean): FeatureSchemaVariants {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'name': json['name'],
'weight': json['weight'],
'weightType': json['weightType'],
'stickiness': json['stickiness'],
'payload': !exists(json, 'payload') ? undefined : json['payload'],
'overrides': !exists(json, 'overrides') ? undefined : ((json['overrides'] as Array<any>).map(FeatureSchemaOverridesFromJSON)),
};
}
export function FeatureSchemaVariantsToJSON(value?: FeatureSchemaVariants | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'name': value.name,
'weight': value.weight,
'weightType': value.weightType,
'stickiness': value.stickiness,
'payload': value.payload,
'overrides': value.overrides === undefined ? undefined : ((value.overrides as Array<any>).map(FeatureSchemaOverridesToJSON)),
};
}

View File

@ -14,11 +14,11 @@
import { exists, mapValues } from '../runtime';
import {
FeatureSchema,
FeatureSchemaFromJSON,
FeatureSchemaFromJSONTyped,
FeatureSchemaToJSON,
} from './FeatureSchema';
FeaturesSchemaFeatures,
FeaturesSchemaFeaturesFromJSON,
FeaturesSchemaFeaturesFromJSONTyped,
FeaturesSchemaFeaturesToJSON,
} from './FeaturesSchemaFeatures';
/**
*
@ -34,10 +34,10 @@ export interface FeaturesSchema {
version: number;
/**
*
* @type {Array<FeatureSchema>}
* @type {Array<FeaturesSchemaFeatures>}
* @memberof FeaturesSchema
*/
features: Array<FeatureSchema>;
features: Array<FeaturesSchemaFeatures>;
}
export function FeaturesSchemaFromJSON(json: any): FeaturesSchema {
@ -51,7 +51,7 @@ export function FeaturesSchemaFromJSONTyped(json: any, ignoreDiscriminator: bool
return {
'version': json['version'],
'features': ((json['features'] as Array<any>).map(FeatureSchemaFromJSON)),
'features': ((json['features'] as Array<any>).map(FeaturesSchemaFeaturesFromJSON)),
};
}
@ -65,7 +65,7 @@ export function FeaturesSchemaToJSON(value?: FeaturesSchema | null): any {
return {
'version': value.version,
'features': ((value.features as Array<any>).map(FeatureSchemaToJSON)),
'features': ((value.features as Array<any>).map(FeaturesSchemaFeaturesToJSON)),
};
}

View File

@ -0,0 +1,149 @@
/* tslint:disable */
/* eslint-disable */
/**
* Unleash API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 4.10.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
import {
FeatureSchemaStrategies,
FeatureSchemaStrategiesFromJSON,
FeatureSchemaStrategiesFromJSONTyped,
FeatureSchemaStrategiesToJSON,
} from './FeatureSchemaStrategies';
import {
FeatureSchemaVariants,
FeatureSchemaVariantsFromJSON,
FeatureSchemaVariantsFromJSONTyped,
FeatureSchemaVariantsToJSON,
} from './FeatureSchemaVariants';
/**
*
* @export
* @interface FeaturesSchemaFeatures
*/
export interface FeaturesSchemaFeatures {
/**
*
* @type {string}
* @memberof FeaturesSchemaFeatures
*/
name: string;
/**
*
* @type {string}
* @memberof FeaturesSchemaFeatures
*/
type?: string;
/**
*
* @type {string}
* @memberof FeaturesSchemaFeatures
*/
description?: string;
/**
*
* @type {string}
* @memberof FeaturesSchemaFeatures
*/
project: string;
/**
*
* @type {boolean}
* @memberof FeaturesSchemaFeatures
*/
enabled?: boolean;
/**
*
* @type {boolean}
* @memberof FeaturesSchemaFeatures
*/
stale?: boolean;
/**
*
* @type {boolean}
* @memberof FeaturesSchemaFeatures
*/
impressionData?: boolean;
/**
*
* @type {Date}
* @memberof FeaturesSchemaFeatures
*/
createdAt?: Date | null;
/**
*
* @type {Date}
* @memberof FeaturesSchemaFeatures
*/
lastSeenAt?: Date | null;
/**
*
* @type {Array<FeatureSchemaStrategies>}
* @memberof FeaturesSchemaFeatures
*/
strategies?: Array<FeatureSchemaStrategies>;
/**
*
* @type {Array<FeatureSchemaVariants>}
* @memberof FeaturesSchemaFeatures
*/
variants?: Array<FeatureSchemaVariants>;
}
export function FeaturesSchemaFeaturesFromJSON(json: any): FeaturesSchemaFeatures {
return FeaturesSchemaFeaturesFromJSONTyped(json, false);
}
export function FeaturesSchemaFeaturesFromJSONTyped(json: any, ignoreDiscriminator: boolean): FeaturesSchemaFeatures {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'name': json['name'],
'type': !exists(json, 'type') ? undefined : json['type'],
'description': !exists(json, 'description') ? undefined : json['description'],
'project': json['project'],
'enabled': !exists(json, 'enabled') ? undefined : json['enabled'],
'stale': !exists(json, 'stale') ? undefined : json['stale'],
'impressionData': !exists(json, 'impressionData') ? undefined : json['impressionData'],
'createdAt': !exists(json, 'createdAt') ? undefined : (json['createdAt'] === null ? null : new Date(json['createdAt'])),
'lastSeenAt': !exists(json, 'lastSeenAt') ? undefined : (json['lastSeenAt'] === null ? null : new Date(json['lastSeenAt'])),
'strategies': !exists(json, 'strategies') ? undefined : ((json['strategies'] as Array<any>).map(FeatureSchemaStrategiesFromJSON)),
'variants': !exists(json, 'variants') ? undefined : ((json['variants'] as Array<any>).map(FeatureSchemaVariantsFromJSON)),
};
}
export function FeaturesSchemaFeaturesToJSON(value?: FeaturesSchemaFeatures | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'name': value.name,
'type': value.type,
'description': value.description,
'project': value.project,
'enabled': value.enabled,
'stale': value.stale,
'impressionData': value.impressionData,
'createdAt': value.createdAt === undefined ? undefined : (value.createdAt === null ? null : value.createdAt.toISOString().substr(0,10)),
'lastSeenAt': value.lastSeenAt === undefined ? undefined : (value.lastSeenAt === null ? null : value.lastSeenAt.toISOString().substr(0,10)),
'strategies': value.strategies === undefined ? undefined : ((value.strategies as Array<any>).map(FeatureSchemaStrategiesToJSON)),
'variants': value.variants === undefined ? undefined : ((value.variants as Array<any>).map(FeatureSchemaVariantsToJSON)),
};
}

View File

@ -14,11 +14,11 @@
import { exists, mapValues } from '../runtime';
import {
ConstraintSchema,
ConstraintSchemaFromJSON,
ConstraintSchemaFromJSONTyped,
ConstraintSchemaToJSON,
} from './ConstraintSchema';
CreateStrategySchemaConstraints,
CreateStrategySchemaConstraintsFromJSON,
CreateStrategySchemaConstraintsFromJSONTyped,
CreateStrategySchemaConstraintsToJSON,
} from './CreateStrategySchemaConstraints';
/**
*
@ -40,16 +40,16 @@ export interface StrategySchema {
name: string;
/**
*
* @type {Array<ConstraintSchema>}
* @type {Array<CreateStrategySchemaConstraints>}
* @memberof StrategySchema
*/
constraints: Array<ConstraintSchema>;
constraints: Array<CreateStrategySchemaConstraints>;
/**
*
* @type {object}
* @type {{ [key: string]: string; }}
* @memberof StrategySchema
*/
parameters: object;
parameters: { [key: string]: string; };
}
export function StrategySchemaFromJSON(json: any): StrategySchema {
@ -64,7 +64,7 @@ export function StrategySchemaFromJSONTyped(json: any, ignoreDiscriminator: bool
'id': json['id'],
'name': json['name'],
'constraints': ((json['constraints'] as Array<any>).map(ConstraintSchemaFromJSON)),
'constraints': ((json['constraints'] as Array<any>).map(CreateStrategySchemaConstraintsFromJSON)),
'parameters': json['parameters'],
};
}
@ -80,7 +80,7 @@ export function StrategySchemaToJSON(value?: StrategySchema | null): any {
'id': value.id,
'name': value.name,
'constraints': ((value.constraints as Array<any>).map(ConstraintSchemaToJSON)),
'constraints': ((value.constraints as Array<any>).map(CreateStrategySchemaConstraintsToJSON)),
'parameters': value.parameters,
};
}

View File

@ -14,11 +14,11 @@
import { exists, mapValues } from '../runtime';
import {
OverrideSchema,
OverrideSchemaFromJSON,
OverrideSchemaFromJSONTyped,
OverrideSchemaToJSON,
} from './OverrideSchema';
FeatureSchemaOverrides,
FeatureSchemaOverridesFromJSON,
FeatureSchemaOverridesFromJSONTyped,
FeatureSchemaOverridesToJSON,
} from './FeatureSchemaOverrides';
/**
*
@ -58,10 +58,10 @@ export interface VariantSchema {
payload?: object;
/**
*
* @type {Array<OverrideSchema>}
* @type {Array<FeatureSchemaOverrides>}
* @memberof VariantSchema
*/
overrides: Array<OverrideSchema>;
overrides?: Array<FeatureSchemaOverrides>;
}
export function VariantSchemaFromJSON(json: any): VariantSchema {
@ -79,7 +79,7 @@ export function VariantSchemaFromJSONTyped(json: any, ignoreDiscriminator: boole
'weightType': json['weightType'],
'stickiness': json['stickiness'],
'payload': !exists(json, 'payload') ? undefined : json['payload'],
'overrides': ((json['overrides'] as Array<any>).map(OverrideSchemaFromJSON)),
'overrides': !exists(json, 'overrides') ? undefined : ((json['overrides'] as Array<any>).map(FeatureSchemaOverridesFromJSON)),
};
}
@ -97,7 +97,7 @@ export function VariantSchemaToJSON(value?: VariantSchema | null): any {
'weightType': value.weightType,
'stickiness': value.stickiness,
'payload': value.payload,
'overrides': ((value.overrides as Array<any>).map(OverrideSchemaToJSON)),
'overrides': value.overrides === undefined ? undefined : ((value.overrides as Array<any>).map(FeatureSchemaOverridesToJSON)),
};
}

View File

@ -1,9 +1,16 @@
/* tslint:disable */
/* eslint-disable */
export * from './ChangeProjectSchema';
export * from './ConstraintSchema';
export * from './CreateFeatureSchema';
export * from './CreateStrategySchema';
export * from './CreateStrategySchemaConstraints';
export * from './FeatureSchema';
export * from './FeatureSchemaOverrides';
export * from './FeatureSchemaStrategies';
export * from './FeatureSchemaVariants';
export * from './FeaturesSchema';
export * from './FeaturesSchemaFeatures';
export * from './OverrideSchema';
export * from './StrategySchema';
export * from './VariantSchema';

View File

@ -0,0 +1,31 @@
import { cleanConstraint } from 'utils/cleanConstraint';
test('cleanConstraint values', () => {
expect(
cleanConstraint({
contextName: '',
operator: 'IN',
value: '1',
values: ['2'],
})
).toEqual({
contextName: '',
operator: 'IN',
values: ['2'],
});
});
test('cleanConstraint value', () => {
expect(
cleanConstraint({
contextName: '',
operator: 'NUM_EQ',
value: '1',
values: ['2'],
})
).toEqual({
contextName: '',
operator: 'NUM_EQ',
value: '1',
});
});

View File

@ -1,31 +1,16 @@
import { singleValueOperators } from 'constants/operators';
import { IConstraint } from 'interfaces/strategy';
import { oneOf } from 'utils/oneOf';
const VALUES = 'values';
const VALUE = 'value';
import produce from 'immer';
export const cleanConstraint = (
constraint: Readonly<IConstraint>
): IConstraint => {
const constraintCopy: IConstraint = {
contextName: '',
operator: 'IN',
};
if (oneOf(singleValueOperators, constraint.operator)) {
for (const [key, value] of Object.entries(constraint)) {
if (key !== VALUES) {
constraintCopy[key] = value;
}
return produce(constraint, draft => {
if (oneOf(singleValueOperators, constraint.operator)) {
delete draft.values;
} else {
delete draft.value;
}
return constraintCopy;
} else {
for (const [key, value] of Object.entries(constraint)) {
if (key !== VALUE) {
constraintCopy[key] = value;
}
}
return constraintCopy;
}
});
};

View File

@ -1,4 +1,8 @@
import { IStrategy, IParameter } from '../interfaces/strategy';
import {
IStrategy,
IStrategyParameter,
IFeatureStrategyParameters,
} from 'interfaces/strategy';
import { resolveDefaultParamValue } from 'utils/resolveDefaultParamValue';
export const getStrategyObject = (
@ -9,9 +13,10 @@ export const getStrategyObject = (
const selectedStrategy = selectableStrategies.find(
strategy => strategy.name === name
);
const parameters = {} as IParameter;
selectedStrategy?.parameters.forEach(({ name }: IParameter) => {
const parameters: IFeatureStrategyParameters = {};
selectedStrategy?.parameters.forEach(({ name }: IStrategyParameter) => {
parameters[name] = resolveDefaultParamValue(name, featureId);
});

View File

@ -0,0 +1,36 @@
import {
parseParameterNumber,
parseParameterString,
parseParameterStrings,
} from 'utils/parseParameter';
test('parseParameterNumber', () => {
expect(parseParameterNumber(undefined)).toEqual(0);
expect(parseParameterNumber('')).toEqual(0);
expect(parseParameterNumber(0)).toEqual(0);
expect(parseParameterNumber(1)).toEqual(1);
expect(parseParameterNumber('a')).toEqual(0);
expect(parseParameterNumber('0')).toEqual(0);
expect(parseParameterNumber('1')).toEqual(1);
});
test('parseParameterString', () => {
expect(parseParameterString(undefined)).toEqual('');
expect(parseParameterString('')).toEqual('');
expect(parseParameterString(0)).toEqual('0');
expect(parseParameterString(1)).toEqual('1');
expect(parseParameterString('a')).toEqual('a');
expect(parseParameterString('0')).toEqual('0');
expect(parseParameterString('1')).toEqual('1');
expect(parseParameterString(' a, ,a ')).toEqual('a, ,a');
});
test('parseParameterStrings', () => {
expect(parseParameterStrings(undefined)).toEqual([]);
expect(parseParameterStrings('')).toEqual([]);
expect(parseParameterStrings(0)).toEqual(['0']);
expect(parseParameterStrings(1)).toEqual(['1']);
expect(parseParameterStrings('a')).toEqual(['a']);
expect(parseParameterStrings('a,b,c')).toEqual(['a', 'b', 'c']);
expect(parseParameterStrings(' a,, b c ,,, d,')).toEqual(['a', 'b c', 'd']);
});

View File

@ -0,0 +1,23 @@
import { IFeatureStrategyParameters } from 'interfaces/strategy';
export const parseParameterNumber = (
value: IFeatureStrategyParameters[string]
): number => {
const parsed = Number(parseParameterString(value));
return Number.isFinite(parsed) ? parsed : 0;
};
export const parseParameterString = (
value: IFeatureStrategyParameters[string]
): string => {
return String(value ?? '').trim();
};
export const parseParameterStrings = (
value: IFeatureStrategyParameters[string]
): string[] => {
return parseParameterString(value)
.split(',')
.map(s => s.trim())
.filter(Boolean);
};

View File

@ -1,11 +1,11 @@
export const resolveDefaultParamValue = (
name: string,
featureToggleName: string
): string | number => {
): string => {
switch (name) {
case 'percentage':
case 'rollout':
return 100;
return '100';
case 'stickiness':
return 'default';
case 'groupId':