fix(ui): some ui fixes for feature flags
This commit is contained in:
parent
f903649faa
commit
2980886b31
7 changed files with 77 additions and 54 deletions
|
|
@ -13,11 +13,11 @@ function FFlagItem({ flag }: { flag: FeatureFlag }) {
|
|||
const newValue = !flag.isActive
|
||||
flag.setIsEnabled(newValue);
|
||||
featureFlagsStore.updateFlagStatus(flag.featureFlagId, newValue).then(() => {
|
||||
toast.success('Feature flag updated.');
|
||||
toast.success('Feature flag status has been updated.');
|
||||
})
|
||||
.catch(() => {
|
||||
flag.setIsEnabled(!newValue);
|
||||
toast.error('Failed to update flag.')
|
||||
toast.error('Something went wrong, please try again.')
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -25,16 +25,16 @@ function FFlagItem({ flag }: { flag: FeatureFlag }) {
|
|||
const flagOwner = flag.updatedBy || flag.createdBy
|
||||
const user = userStore.list.length > 0 ? userStore.list.find(u => parseInt(u.userId) === flagOwner!)?.name : flagOwner;
|
||||
return (
|
||||
<div className={'w-full py-2 px-6 border-b'}>
|
||||
<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}`}>
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<Icon name={flagIcon} size={32} />
|
||||
<div className="flex flex-col gap-2" style={{ width: 200 }}>
|
||||
<div className="flex flex-col gap-1" style={{ width: 200 }}>
|
||||
<span className={'link'}>{flag.flagKey}</span>
|
||||
{flag.description
|
||||
? (
|
||||
<TextEllipsis hintText={flag.description} text={flag.description} className={'text-disabled-text !no-underline'} />
|
||||
<TextEllipsis hintText={flag.description} text={flag.description} style={{ color: 'rgba(0,0,0, 0.6)'}} />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import FFlagsListHeader from 'Components/FFlags/FFlagsListHeader';
|
||||
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
||||
import {Loader, NoContent, Pagination} from 'UI';
|
||||
import { Loader, NoContent, Pagination } from 'UI';
|
||||
import FFlagItem from './FFlagItem';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
|
@ -29,14 +29,18 @@ function FFlagsList({ siteId }: { siteId: string }) {
|
|||
<div className={'flex flex-col items-center justify-center'}>
|
||||
<AnimatedSVG name={ICONS.NO_FFLAGS} size={285} />
|
||||
<div className="text-center text-gray-600 mt-4">
|
||||
You haven't created any feature flags yet.
|
||||
{featureFlagsStore.sort.query === ''
|
||||
? "You haven't created any feature flags yet."
|
||||
: 'No matching results'}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
subtext={
|
||||
<div className="text-center flex justify-center items-center flex-col">
|
||||
Use feature flags to deploy and rollback new functionality with ease.
|
||||
</div>
|
||||
featureFlagsStore.sort.query === '' ? (
|
||||
<div className="text-center flex justify-center items-center flex-col">
|
||||
Use feature flags to deploy and rollback new functionality with ease.
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ function Header({ current, onCancel, onSave, isNew }: any) {
|
|||
return (
|
||||
<>
|
||||
<div>
|
||||
<h1 className={cn('text-2xl')}>{isNew ? 'New Feature Flag' : current.flagKey}</h1>
|
||||
<h1 className={cn('text-2xl')}>{!current.flagKey ? 'New Feature Flag' : current.flagKey}</h1>
|
||||
</div>
|
||||
|
||||
<div className={'flex items-center gap-2'}>
|
||||
|
|
|
|||
|
|
@ -57,13 +57,12 @@ function Multivariant() {
|
|||
</div>
|
||||
<div style={{ flex: 4 }} className={'flex items-center'}>
|
||||
<Rollout />
|
||||
<Button
|
||||
variant={'text-primary'}
|
||||
className={'font-normal ml-auto'}
|
||||
<div
|
||||
className={"ml-auto text-blue font-normal cursor-pointer mr-10"}
|
||||
onClick={featureFlagsStore.currentFflag!.redistributeVariants}
|
||||
>
|
||||
Distribute Equally
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -77,7 +76,7 @@ function Multivariant() {
|
|||
</div>
|
||||
<div style={{ flex: 4 }}>
|
||||
<Input
|
||||
placeholder={'buy-btn-variant-1'}
|
||||
placeholder={`buy-btn-variant-${ind+1}`}
|
||||
value={variant.value}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
variant.setKey(e.target.value)
|
||||
|
|
@ -86,7 +85,7 @@ function Multivariant() {
|
|||
</div>
|
||||
<div style={{ flex: 4 }}>
|
||||
<Input
|
||||
placeholder={'Very red button'}
|
||||
placeholder={'Enter here...'}
|
||||
value={variant.description}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
variant.setDescription(e.target.value)
|
||||
|
|
@ -95,7 +94,7 @@ function Multivariant() {
|
|||
</div>
|
||||
<div style={{ flex: 4 }}>
|
||||
<Input
|
||||
placeholder={"Example: very important button, {'buttonColor': 'red'}"}
|
||||
placeholder={"E.g. very important button, {'buttonColor': 'red'}"}
|
||||
value={variant.payload}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
variant.setPayload(e.target.value)
|
||||
|
|
@ -136,12 +135,12 @@ function Multivariant() {
|
|||
);
|
||||
})}
|
||||
</div>
|
||||
<div className={'mt-2 flex justify-between w-full pr-4'}>
|
||||
<div className={'mt-2 flex justify-between w-full'}>
|
||||
<Button variant={'text-primary'} onClick={featureFlagsStore.currentFflag!.addVariant}>
|
||||
+ Add Variant
|
||||
</Button>
|
||||
{featureFlagsStore.currentFflag!.isRedDistribution ? (
|
||||
<div className={'text-red'}>Total distribution is less than 100%</div>
|
||||
<div className={'text-red mr-10'}>Total distribution is less than 100%.</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Input, SegmentSelection, Toggler, Loader, Button, NoContent } from 'UI'
|
|||
import Breadcrumb from 'Shared/Breadcrumb';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
import HowTo from 'Components/FFlags/NewFFlag/HowTo';
|
||||
import { useHistory } from 'react-router';
|
||||
import {Prompt, useHistory} from 'react-router';
|
||||
import { withSiteId, fflags } from 'App/routes';
|
||||
import Description from './Description';
|
||||
import Header from './Header';
|
||||
|
|
@ -76,6 +76,12 @@ function NewFFlag({ siteId, fflagId }: { siteId: string; fflagId?: string }) {
|
|||
const showDescription = Boolean(current.description?.length);
|
||||
return (
|
||||
<div className={'w-full mx-auto mb-4'} style={{ maxWidth: 1300 }}>
|
||||
<Prompt
|
||||
when={current.hasChanged}
|
||||
message={() => {
|
||||
return 'You have unsaved changes. Are you sure you want to leave?';
|
||||
}}
|
||||
/>
|
||||
<Breadcrumb
|
||||
items={[
|
||||
{ label: 'Feature Flags', to: withSiteId(fflags(), siteId) },
|
||||
|
|
@ -84,12 +90,7 @@ function NewFFlag({ siteId, fflagId }: { siteId: string; fflagId?: string }) {
|
|||
/>
|
||||
<div className={'w-full bg-white rounded p-4 widget-wrapper'}>
|
||||
<div className="flex justify-between items-center">
|
||||
<Header
|
||||
current={current}
|
||||
onCancel={onCancel}
|
||||
onSave={onSave}
|
||||
isNew={!fflagId}
|
||||
/>
|
||||
<Header current={current} onCancel={onCancel} onSave={onSave} isNew={!fflagId} />
|
||||
</div>
|
||||
<div className={'w-full border-b border-light-gray my-2'} />
|
||||
|
||||
|
|
@ -134,20 +135,22 @@ function NewFFlag({ siteId, fflagId }: { siteId: string; fflagId?: string }) {
|
|||
{ name: 'Single Variant (Boolean)', value: 'single' },
|
||||
{ name: 'Multi-Variant (String)', value: 'multi' },
|
||||
]}
|
||||
|
||||
/>
|
||||
</div>
|
||||
{current.isSingleOption ? (
|
||||
<>
|
||||
<div className={'text-sm text-disabled-text mt-1 flex items-center gap-1'}>
|
||||
Users will be served
|
||||
<code className={'p-1 text-red rounded bg-gray-lightest'}>true</code> if they match
|
||||
one or more rollout conditions.
|
||||
</div>
|
||||
<div className={"mt-6"}>
|
||||
<Payload />
|
||||
<Input placeholder={"Example: very important button, {'buttonColor': 'red'}"} className={'mt-2'} />
|
||||
</div>
|
||||
<div className={'text-sm text-disabled-text mt-1 flex items-center gap-1'}>
|
||||
Users will be served
|
||||
<code className={'p-1 text-red rounded bg-gray-lightest'}>true</code> if they match
|
||||
one or more rollout conditions.
|
||||
</div>
|
||||
<div className={'mt-6'}>
|
||||
<Payload />
|
||||
<Input
|
||||
placeholder={"E.g. very important button, {'buttonColor': 'red'}"}
|
||||
className={'mt-2'}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<Multivariant />
|
||||
|
|
@ -183,19 +186,17 @@ function NewFFlag({ siteId, fflagId }: { siteId: string; fflagId?: string }) {
|
|||
|
||||
<div className={'mt-6 p-4 rounded bg-gray-lightest'}>
|
||||
<label className={'font-semibold'}>Rollout Conditions</label>
|
||||
{current.conditions.length === 0 ? null
|
||||
: (
|
||||
<div className={'text-sm text-disabled-text mb-2'}>
|
||||
Indicate the users for whom you intend to make this flag available. Keep in mind that
|
||||
each set of conditions will be deployed separately from one another.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{current.conditions.length === 0 ? null : (
|
||||
<div className={'text-sm text-disabled-text mb-2'}>
|
||||
Indicate the users for whom you intend to make this flag available. Keep in mind that
|
||||
each set of conditions will be deployed separately from one another.
|
||||
</div>
|
||||
)}
|
||||
<NoContent
|
||||
show={current.conditions.length === 0}
|
||||
title={'100% of sessions will get this feature flag'}
|
||||
subtext={
|
||||
<div className={"flex flex-col items-center"}>
|
||||
<div className={'flex flex-col items-center'}>
|
||||
<div className={'text-sm mb-1'}>
|
||||
Indicate the users for whom you intend to make this flag available.
|
||||
</div>
|
||||
|
|
@ -217,14 +218,16 @@ function NewFFlag({ siteId, fflagId }: { siteId: string; fflagId?: string }) {
|
|||
<div className={'my-2 w-full text-center'}>OR</div>
|
||||
</React.Fragment>
|
||||
))}
|
||||
<div
|
||||
onClick={() => current!.addCondition()}
|
||||
className={
|
||||
'flex items-center justify-center w-full bg-white rounded border mt-2 p-2'
|
||||
}
|
||||
>
|
||||
<Button variant={'text-primary'}>+ Create Condition Set</Button>
|
||||
</div>
|
||||
{current.conditions.length <= 10 ? (
|
||||
<div
|
||||
onClick={() => current!.addCondition()}
|
||||
className={
|
||||
'flex items-center justify-center w-full bg-white rounded border mt-2 p-2'
|
||||
}
|
||||
>
|
||||
<Button variant={'text-primary'}>+ Create Condition Set</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
</NoContent>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ export default class FeatureFlagsStore {
|
|||
this.client = customClient ?? fflagsService
|
||||
}
|
||||
|
||||
|
||||
|
||||
setFlagsSearch = (search: string) => {
|
||||
this.flagsSearch = search;
|
||||
};
|
||||
|
|
@ -122,6 +124,7 @@ export default class FeatureFlagsStore {
|
|||
try {
|
||||
// @ts-ignore
|
||||
const result = await this.client.createFlag(this.currentFflag.toJS());
|
||||
this.currentFflag.setHasChanged(false)
|
||||
this.addFlag(new FeatureFlag(result));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
|
@ -150,6 +153,7 @@ export default class FeatureFlagsStore {
|
|||
// @ts-ignore
|
||||
const result = await this.client.updateFlag(usedFlag.toJS());
|
||||
if (!flag) this.setCurrentFlag(new FeatureFlag(result));
|
||||
if (!flag) this.currentFflag?.setHasChanged(false)
|
||||
} catch (e) {
|
||||
console.error('getting api error', e);
|
||||
throw e.response;
|
||||
|
|
|
|||
|
|
@ -92,6 +92,11 @@ export default class FeatureFlag {
|
|||
payload: SingleFFlag['payload']
|
||||
flagType: string;
|
||||
variants: Variant[] = [];
|
||||
hasChanged = false
|
||||
|
||||
setHasChanged = (hasChanged: boolean) => {
|
||||
this.hasChanged = hasChanged
|
||||
}
|
||||
|
||||
constructor(data?: SingleFFlag) {
|
||||
Object.assign(
|
||||
|
|
@ -109,11 +114,13 @@ export default class FeatureFlag {
|
|||
|
||||
setPayload = (payload: string) => {
|
||||
this.payload = payload;
|
||||
this.setHasChanged(true)
|
||||
}
|
||||
|
||||
addVariant = () => {
|
||||
this.variants.push(new Variant(this.variants.length + 1))
|
||||
this.redistributeVariants()
|
||||
this.setHasChanged(true)
|
||||
}
|
||||
|
||||
removeVariant = (index: number) => {
|
||||
|
|
@ -151,6 +158,7 @@ export default class FeatureFlag {
|
|||
|
||||
addCondition = () => {
|
||||
this.conditions.push(new Conditions())
|
||||
this.setHasChanged(true)
|
||||
}
|
||||
|
||||
removeCondition = (index: number) => {
|
||||
|
|
@ -159,21 +167,26 @@ export default class FeatureFlag {
|
|||
|
||||
setFlagKey = (flagKey: string) => {
|
||||
this.flagKey = flagKey;
|
||||
this.setHasChanged(true)
|
||||
}
|
||||
|
||||
setDescription = (description: string) => {
|
||||
this.description = description;
|
||||
this.setHasChanged(true)
|
||||
}
|
||||
|
||||
setIsPersist = (isPersist: boolean) => {
|
||||
this.isPersist = isPersist;
|
||||
this.setHasChanged(true)
|
||||
}
|
||||
|
||||
setIsSingleOption = (isSingleOption: boolean) => {
|
||||
this.isSingleOption = isSingleOption;
|
||||
this.setHasChanged(true)
|
||||
}
|
||||
|
||||
setIsEnabled = (isEnabled: boolean) => {
|
||||
this.isActive = isEnabled;
|
||||
this.setHasChanged(true)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue