fix(ui): re-add flag read view
This commit is contained in:
parent
282aa19847
commit
c9c5e68283
7 changed files with 197 additions and 119 deletions
|
|
@ -5,6 +5,7 @@ import { useStore } from 'App/mstore';
|
|||
import { observer } from 'mobx-react-lite';
|
||||
import { resentOrDate } from 'App/date';
|
||||
import { toast } from 'react-toastify';
|
||||
import { fflagRead } from "App/routes";
|
||||
|
||||
function FFlagItem({ flag }: { flag: FeatureFlag }) {
|
||||
const { featureFlagsStore, userStore } = useStore();
|
||||
|
|
@ -32,7 +33,7 @@ function FFlagItem({ flag }: { flag: FeatureFlag }) {
|
|||
return (
|
||||
<div className={'w-full py-2 px-6 border-b hover:bg-active-blue'}>
|
||||
<div className={'flex items-center'}>
|
||||
<Link style={{ flex: 1 }} to={`feature-flags/${flag.featureFlagId}`}>
|
||||
<Link style={{ flex: 1 }} to={fflagRead(flag.featureFlagId.toString())}>
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<Tooltip delay={150} title={flag.isSingleOption ? 'Single variant' : 'Multivariant'}>
|
||||
<Icon name={flagIcon} size={32} />
|
||||
|
|
|
|||
|
|
@ -100,10 +100,12 @@ function FlagView({ siteId, fflagId }: { siteId: string; fflagId: string }) {
|
|||
<React.Fragment key={index}>
|
||||
<RolloutCondition
|
||||
set={index + 1}
|
||||
readonly
|
||||
index={index}
|
||||
conditions={condition}
|
||||
removeCondition={current.removeCondition}
|
||||
/>
|
||||
<div className={'mt-2'} />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ interface Props {
|
|||
conditions: Conditions;
|
||||
removeCondition: (ind: number) => void;
|
||||
index: number
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
function RolloutCondition({ set, conditions, removeCondition, index }: Props) {
|
||||
function RolloutCondition({ set, conditions, removeCondition, index, readonly }: Props) {
|
||||
const [forceRender, forceRerender] = React.useState(false);
|
||||
const onAddFilter = (filter = {}) => {
|
||||
conditions.filter.addFilter(filter);
|
||||
|
|
@ -47,17 +48,16 @@ function RolloutCondition({ set, conditions, removeCondition, index }: Props) {
|
|||
<div className={'flex items-center border-b px-4 py-2 gap-2'}>
|
||||
<div>Condition</div>
|
||||
<div className={'p-2 rounded bg-gray-lightest'}>Set {set}</div>
|
||||
<div
|
||||
className={cn(
|
||||
'p-2 px-4 cursor-pointer rounded ml-auto',
|
||||
'hover:bg-teal-light'
|
||||
)}
|
||||
onClick={() => removeCondition(index)}
|
||||
>
|
||||
<Icon name={'trash'} color={'main'} />
|
||||
</div>
|
||||
{readonly ? null : (
|
||||
<div
|
||||
className={cn('p-2 px-4 cursor-pointer rounded ml-auto', 'hover:bg-teal-light')}
|
||||
onClick={() => removeCondition(index)}
|
||||
>
|
||||
<Icon name={'trash'} color={'main'} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={'p-2 border-b'}>
|
||||
<div className={readonly ? 'p-2' : 'p-2 border-b'}>
|
||||
<div className={conditions.filter.filters.length > 0 ? 'p-2 border-b mb-2' : ''}>
|
||||
<FilterList
|
||||
filter={conditions.filter}
|
||||
|
|
@ -66,25 +66,38 @@ function RolloutCondition({ set, conditions, removeCondition, index }: Props) {
|
|||
onChangeEventsOrder={onChangeEventsOrder}
|
||||
hideEventsOrder
|
||||
excludeFilterKeys={nonFlagFilters}
|
||||
readonly
|
||||
/>
|
||||
{readonly && !conditions.filter?.filters?.length ? (
|
||||
<div className={'p-2'}>No conditions</div>
|
||||
) : null}
|
||||
</div>
|
||||
<FilterSelection
|
||||
filter={undefined}
|
||||
onFilterClick={onAddFilter}
|
||||
excludeFilterKeys={nonFlagFilters}
|
||||
>
|
||||
<Button variant="text-primary" icon="plus">Add Condition</Button>
|
||||
</FilterSelection>
|
||||
{readonly ? null : (
|
||||
<FilterSelection
|
||||
filter={undefined}
|
||||
onFilterClick={onAddFilter}
|
||||
excludeFilterKeys={nonFlagFilters}
|
||||
>
|
||||
<Button variant="text-primary" icon="plus">
|
||||
Add Condition
|
||||
</Button>
|
||||
</FilterSelection>
|
||||
)}
|
||||
</div>
|
||||
<div className={'px-4 py-2 flex items-center gap-2'}>
|
||||
<span>Rollout to</span>
|
||||
<Input
|
||||
type="text"
|
||||
width={60}
|
||||
value={conditions.rolloutPercentage}
|
||||
onChange={onPercentChange}
|
||||
leadingButton={<div className={'p-2 text-disabled-text'}>%</div>}
|
||||
/>
|
||||
{readonly ? (
|
||||
<div>{conditions.rolloutPercentage}%</div>
|
||||
) : (
|
||||
<Input
|
||||
type="text"
|
||||
width={60}
|
||||
value={conditions.rolloutPercentage}
|
||||
onChange={onPercentChange}
|
||||
leadingButton={<div className={'p-2 text-disabled-text'}>%</div>}
|
||||
/>
|
||||
)}
|
||||
|
||||
<span>of sessions</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,112 +2,159 @@ import React from 'react';
|
|||
import FilterOperator from '../FilterOperator';
|
||||
import FilterSelection from '../FilterSelection';
|
||||
import FilterValue from '../FilterValue';
|
||||
import { Button } from 'UI';
|
||||
import {Button} from 'UI';
|
||||
import FilterSource from '../FilterSource';
|
||||
import { FilterKey, FilterType } from 'App/types/filter/filterType';
|
||||
import {FilterKey, FilterType} from 'App/types/filter/filterType';
|
||||
import SubFilterItem from '../SubFilterItem';
|
||||
import {toJS} from "mobx";
|
||||
|
||||
interface Props {
|
||||
filterIndex: number;
|
||||
filter: any; // event/filter
|
||||
onUpdate: (filter: any) => void;
|
||||
onRemoveFilter: () => void;
|
||||
isFilter?: boolean;
|
||||
saveRequestPayloads?: boolean;
|
||||
disableDelete?: boolean;
|
||||
excludeFilterKeys?: Array<string>;
|
||||
filterIndex: number;
|
||||
filter: any; // event/filter
|
||||
onUpdate: (filter: any) => void;
|
||||
onRemoveFilter: () => void;
|
||||
isFilter?: boolean;
|
||||
saveRequestPayloads?: boolean;
|
||||
disableDelete?: boolean;
|
||||
excludeFilterKeys?: Array<string>;
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
function FilterItem(props: Props) {
|
||||
const { isFilter = false, filterIndex, filter, saveRequestPayloads, disableDelete = false, excludeFilterKeys = [] } = props;
|
||||
const canShowValues = !(filter.operator === 'isAny' || filter.operator === 'onAny' || filter.operator === 'isUndefined');
|
||||
const isSubFilter = filter.type === FilterType.SUB_FILTERS;
|
||||
const replaceFilter = (filter: any) => {
|
||||
props.onUpdate({
|
||||
...filter,
|
||||
value: [''],
|
||||
filters: filter.filters ? filter.filters.map((i: any) => ({ ...i, value: [''] })) : [],
|
||||
});
|
||||
};
|
||||
const {
|
||||
isFilter = false,
|
||||
filterIndex,
|
||||
filter,
|
||||
saveRequestPayloads,
|
||||
disableDelete = false,
|
||||
excludeFilterKeys = []
|
||||
} = props;
|
||||
const canShowValues = !(filter.operator === 'isAny' || filter.operator === 'onAny' || filter.operator === 'isUndefined');
|
||||
const isSubFilter = filter.type === FilterType.SUB_FILTERS;
|
||||
const replaceFilter = (filter: any) => {
|
||||
props.onUpdate({
|
||||
...filter,
|
||||
value: [''],
|
||||
filters: filter.filters ? filter.filters.map((i: any) => ({...i, value: ['']})) : [],
|
||||
});
|
||||
};
|
||||
|
||||
const onOperatorChange = (e: any, { value }: any) => {
|
||||
props.onUpdate({ ...filter, operator: value });
|
||||
};
|
||||
const onOperatorChange = (e: any, {value}: any) => {
|
||||
props.onUpdate({...filter, operator: value});
|
||||
};
|
||||
|
||||
const onSourceOperatorChange = (e: any, { value }: any) => {
|
||||
props.onUpdate({ ...filter, sourceOperator: value });
|
||||
};
|
||||
const onSourceOperatorChange = (e: any, {value}: any) => {
|
||||
props.onUpdate({...filter, sourceOperator: value});
|
||||
};
|
||||
|
||||
const onUpdateSubFilter = (subFilter: any, subFilterIndex: any) => {
|
||||
props.onUpdate({
|
||||
...filter,
|
||||
filters: filter.filters.map((i: any, index: any) => {
|
||||
if (index === subFilterIndex) {
|
||||
return subFilter;
|
||||
}
|
||||
return i;
|
||||
}),
|
||||
});
|
||||
};
|
||||
const onUpdateSubFilter = (subFilter: any, subFilterIndex: any) => {
|
||||
props.onUpdate({
|
||||
...filter,
|
||||
filters: filter.filters.map((i: any, index: any) => {
|
||||
if (index === subFilterIndex) {
|
||||
return subFilter;
|
||||
}
|
||||
return i;
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center hover:bg-active-blue -mx-5 px-5">
|
||||
<div className="flex items-start w-full">
|
||||
{!isFilter && (
|
||||
<div className="mt-1 flex-shrink-0 border w-6 h-6 text-xs flex items-center justify-center rounded-full bg-gray-light-shade mr-2">
|
||||
<span>{filterIndex + 1}</span>
|
||||
</div>
|
||||
console.log('FilterItem', toJS(filter))
|
||||
return (
|
||||
<div className="flex items-center hover:bg-active-blue -mx-5 px-5">
|
||||
<div className="flex items-start w-full">
|
||||
{!isFilter && (
|
||||
<div
|
||||
className="mt-1 flex-shrink-0 border w-6 h-6 text-xs flex items-center justify-center rounded-full bg-gray-light-shade mr-2">
|
||||
<span>{filterIndex + 1}</span>
|
||||
</div>
|
||||
)}
|
||||
<FilterSelection
|
||||
filter={filter}
|
||||
onFilterClick={replaceFilter}
|
||||
excludeFilterKeys={excludeFilterKeys}
|
||||
disabled={disableDelete || props.readonly}
|
||||
/>
|
||||
|
||||
{/* Filter with Source */}
|
||||
{filter.hasSource && (
|
||||
<>
|
||||
<FilterOperator
|
||||
options={filter.sourceOperatorOptions}
|
||||
onChange={onSourceOperatorChange}
|
||||
className="mx-2 flex-shrink-0"
|
||||
value={filter.sourceOperator}
|
||||
isDisabled={filter.operatorDisabled || props.readonly}
|
||||
/>
|
||||
<FilterSource filter={filter} onUpdate={props.onUpdate}/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Filter values */}
|
||||
{!isSubFilter && filter.operatorOptions && (
|
||||
<>
|
||||
<FilterOperator
|
||||
options={filter.operatorOptions}
|
||||
onChange={onOperatorChange}
|
||||
className="mx-2 flex-shrink-0"
|
||||
value={filter.operator}
|
||||
isDisabled={filter.operatorDisabled || props.readonly}
|
||||
/>
|
||||
{canShowValues && (
|
||||
<>
|
||||
{props.readonly ? (
|
||||
<div
|
||||
className={'px-2 py-1 bg-gray-lightest'}
|
||||
>
|
||||
{filter.value.map((val: string) => {
|
||||
return filter.options && filter.options.length
|
||||
? filter.options[filter.options.findIndex((i: any) => i.value === val)]?.label
|
||||
: val
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<FilterValue filter={filter} onUpdate={props.onUpdate}/>
|
||||
)}
|
||||
<FilterSelection filter={filter} onFilterClick={replaceFilter} excludeFilterKeys={excludeFilterKeys} disabled={disableDelete} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Filter with Source */}
|
||||
{filter.hasSource && (
|
||||
<>
|
||||
<FilterOperator
|
||||
options={filter.sourceOperatorOptions}
|
||||
onChange={onSourceOperatorChange}
|
||||
className="mx-2 flex-shrink-0"
|
||||
value={filter.sourceOperator}
|
||||
isDisabled={filter.operatorDisabled}
|
||||
/>
|
||||
<FilterSource filter={filter} onUpdate={props.onUpdate} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Filter values */}
|
||||
{!isSubFilter && filter.operatorOptions && (
|
||||
<>
|
||||
<FilterOperator
|
||||
options={filter.operatorOptions}
|
||||
onChange={onOperatorChange}
|
||||
className="mx-2 flex-shrink-0"
|
||||
value={filter.operator}
|
||||
isDisabled={filter.operatorDisabled}
|
||||
/>
|
||||
{canShowValues && <FilterValue filter={filter} onUpdate={props.onUpdate} />}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* filters */}
|
||||
{isSubFilter && (
|
||||
<div className="grid grid-col ml-3 w-full">
|
||||
{filter.filters
|
||||
.filter((i: any) => (i.key !== FilterKey.FETCH_REQUEST_BODY && i.key !== FilterKey.FETCH_RESPONSE_BODY) || saveRequestPayloads)
|
||||
.map((subFilter: any, subFilterIndex: any) => (
|
||||
<SubFilterItem
|
||||
filterIndex={subFilterIndex}
|
||||
filter={subFilter}
|
||||
onUpdate={(f) => onUpdateSubFilter(f, subFilterIndex)}
|
||||
onRemoveFilter={props.onRemoveFilter}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 self-start ml-auto">
|
||||
<Button disabled={disableDelete} variant="text" icon="trash" onClick={props.onRemoveFilter} size="small" iconSize={14} />
|
||||
</div>
|
||||
{/* filters */}
|
||||
{isSubFilter && (
|
||||
<div className="grid grid-col ml-3 w-full">
|
||||
{filter.filters
|
||||
.filter(
|
||||
(i: any) =>
|
||||
(i.key !== FilterKey.FETCH_REQUEST_BODY &&
|
||||
i.key !== FilterKey.FETCH_RESPONSE_BODY) ||
|
||||
saveRequestPayloads
|
||||
)
|
||||
.map((subFilter: any, subFilterIndex: any) => (
|
||||
<SubFilterItem
|
||||
filterIndex={subFilterIndex}
|
||||
filter={subFilter}
|
||||
onUpdate={(f) => onUpdateSubFilter(f, subFilterIndex)}
|
||||
onRemoveFilter={props.onRemoveFilter}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{props.readonly ? null :
|
||||
<div className="flex flex-shrink-0 self-start ml-auto">
|
||||
<Button
|
||||
disabled={disableDelete}
|
||||
variant="text"
|
||||
icon="trash"
|
||||
onClick={props.onRemoveFilter}
|
||||
size="small"
|
||||
iconSize={14}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterItem;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ interface Props {
|
|||
observeChanges?: () => void;
|
||||
saveRequestPayloads?: boolean;
|
||||
supportsEmpty?: boolean
|
||||
readonly?: boolean;
|
||||
excludeFilterKeys?: Array<string>
|
||||
}
|
||||
function FilterList(props: Props) {
|
||||
|
|
@ -84,6 +85,7 @@ function FilterList(props: Props) {
|
|||
saveRequestPayloads={saveRequestPayloads}
|
||||
disableDelete={cannotDeleteFilter}
|
||||
excludeFilterKeys={excludeFilterKeys}
|
||||
readonly={props.readonly}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
|
|
@ -99,6 +101,7 @@ function FilterList(props: Props) {
|
|||
!filter.isEvent ? (
|
||||
<FilterItem
|
||||
key={filterIndex}
|
||||
readonly={props.readonly}
|
||||
isFilter={true}
|
||||
filterIndex={filterIndex}
|
||||
filter={filter}
|
||||
|
|
|
|||
|
|
@ -202,6 +202,14 @@ export default class API {
|
|||
return this.featureFlags.reloadFlags()
|
||||
}
|
||||
|
||||
getFeatureFlag(flagName: string): IFeatureFlag | undefined {
|
||||
return this.featureFlags.getFeatureFlag(flagName)
|
||||
}
|
||||
|
||||
getAllFeatureFlags() {
|
||||
return this.featureFlags.flags
|
||||
}
|
||||
|
||||
use<T>(fn: (app: App | null, options?: Options) => T): T {
|
||||
return fn(this.app, this.options)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ export default class FeatureFlags {
|
|||
}
|
||||
}
|
||||
|
||||
getFeatureFlag(flagName: string): IFeatureFlag | undefined {
|
||||
return this.flags.find((flag) => flag.key === flagName)
|
||||
}
|
||||
|
||||
isFlagEnabled(flagName: string): boolean {
|
||||
return this.flags.findIndex((flag) => flag.key === flagName) !== -1
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue