fix(ui): some ui fixes for feature flags

This commit is contained in:
nick-delirium 2023-06-23 11:57:14 +02:00
parent f903649faa
commit 2980886b31
7 changed files with 77 additions and 54 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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'}>

View file

@ -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>

View file

@ -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>

View file

@ -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;

View file

@ -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)
}
}