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:
parent
7b60ef2aa6
commit
35262e404b
@ -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);
|
||||
|
@ -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('');
|
||||
};
|
||||
|
||||
|
@ -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 ?? [],
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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"
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -72,7 +72,7 @@ export const useStyles = makeStyles()(theme => ({
|
||||
color: 'inherit',
|
||||
},
|
||||
button: {
|
||||
background: 'white',
|
||||
background: 'white !important',
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
}));
|
||||
|
@ -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={
|
||||
|
@ -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 };
|
||||
|
@ -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 };
|
||||
}
|
||||
|
||||
|
@ -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 };
|
||||
}
|
||||
|
||||
|
@ -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}`}>
|
||||
|
@ -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);
|
||||
|
@ -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({
|
||||
|
@ -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(
|
||||
|
@ -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' });
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>> {
|
||||
|
56
frontend/src/openapi/models/ChangeProjectSchema.ts
Normal file
56
frontend/src/openapi/models/ChangeProjectSchema.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
87
frontend/src/openapi/models/CreateStrategySchema.ts
Normal file
87
frontend/src/openapi/models/CreateStrategySchema.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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)),
|
||||
};
|
||||
}
|
||||
|
||||
|
64
frontend/src/openapi/models/FeatureSchemaOverrides.ts
Normal file
64
frontend/src/openapi/models/FeatureSchemaOverrides.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
|
87
frontend/src/openapi/models/FeatureSchemaStrategies.ts
Normal file
87
frontend/src/openapi/models/FeatureSchemaStrategies.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
|
103
frontend/src/openapi/models/FeatureSchemaVariants.ts
Normal file
103
frontend/src/openapi/models/FeatureSchemaVariants.ts
Normal 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)),
|
||||
};
|
||||
}
|
||||
|
@ -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)),
|
||||
};
|
||||
}
|
||||
|
||||
|
149
frontend/src/openapi/models/FeaturesSchemaFeatures.ts
Normal file
149
frontend/src/openapi/models/FeaturesSchemaFeatures.ts
Normal 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)),
|
||||
};
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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)),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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';
|
||||
|
31
frontend/src/utils/cleanConstraint.test.ts
Normal file
31
frontend/src/utils/cleanConstraint.test.ts
Normal 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',
|
||||
});
|
||||
});
|
@ -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;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
36
frontend/src/utils/parseParameter.test.ts
Normal file
36
frontend/src/utils/parseParameter.test.ts
Normal 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']);
|
||||
});
|
23
frontend/src/utils/parseParameter.ts
Normal file
23
frontend/src/utils/parseParameter.ts
Normal 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);
|
||||
};
|
@ -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':
|
||||
|
Loading…
Reference in New Issue
Block a user