fix(ui): texts for feature flags, colors and stuff

This commit is contained in:
nick-delirium 2023-06-23 16:21:30 +02:00
parent 643cd07bea
commit b41dd03276
10 changed files with 203 additions and 42 deletions

View file

@ -67,6 +67,7 @@ const SESSIONS_PATH = routes.sessions();
const FFLAGS_PATH = routes.fflags();
const FFLAG_PATH = routes.fflag();
const FFLAG_CREATE_PATH = routes.newFFlag();
const FFLAG_READ_PATH = routes.fflagRead()
const NOTES_PATH = routes.notes();
const BOOKMARKS_PATH = routes.bookmarks();
const ASSIST_PATH = routes.assist();
@ -235,12 +236,20 @@ class Router extends React.Component {
<Route exact strict path={withSiteId(FUNNEL_PATH, siteIdList)} component={FunnelPage} />
<Route exact strict path={withSiteId(FUNNEL_CREATE_PATH, siteIdList)} component={FunnelsDetails} />
<Route exact strict path={withSiteId(FUNNEL_ISSUE_PATH, siteIdList)} component={FunnelIssue} />
<Route exact strict path={withSiteId(SESSIONS_PATH, siteIdList)} component={SessionsOverview} />
<Route exact strict path={withSiteId(FFLAGS_PATH, siteIdList)} component={SessionsOverview} />
<Route exact strict path={withSiteId(FFLAG_PATH, siteIdList)} component={SessionsOverview} />
<Route exact strict path={withSiteId(FFLAG_CREATE_PATH, siteIdList)} component={SessionsOverview} />
<Route exact strict path={withSiteId(NOTES_PATH, siteIdList)} component={SessionsOverview} />
<Route exact strict path={withSiteId(BOOKMARKS_PATH, siteIdList)} component={SessionsOverview} />
<Route
exact
strict
path={[
withSiteId(SESSIONS_PATH, siteIdList),
withSiteId(FFLAGS_PATH, siteIdList),
withSiteId(FFLAG_PATH, siteIdList),
withSiteId(FFLAG_READ_PATH, siteIdList),
withSiteId(FFLAG_CREATE_PATH, siteIdList),
withSiteId(NOTES_PATH, siteIdList),
withSiteId(BOOKMARKS_PATH, siteIdList),
]}
component={SessionsOverview}
/>
<Route exact strict path={withSiteId(SESSION_PATH, siteIdList)} component={Session} />
<Route exact strict path={withSiteId(LIVE_SESSION_PATH, siteIdList)} component={LiveSession} />
<Route exact strict path={withSiteId(LIVE_SESSION_PATH, siteIdList)} render={(props) => <Session {...props} live />} />

View file

@ -1,6 +1,6 @@
import React from 'react'
import FeatureFlag from 'App/mstore/types/FeatureFlag'
import { Icon, Toggler, Link, TextEllipsis } from 'UI'
import React from 'react';
import FeatureFlag from 'App/mstore/types/FeatureFlag';
import { Icon, Toggler, Link, TextEllipsis, Tooltip } from 'UI';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import { resentOrDate } from 'App/date';
@ -10,32 +10,42 @@ function FFlagItem({ flag }: { flag: FeatureFlag }) {
const { featureFlagsStore, userStore } = useStore();
const toggleActivity = () => {
const newValue = !flag.isActive
const newValue = !flag.isActive;
flag.setIsEnabled(newValue);
featureFlagsStore.updateFlagStatus(flag.featureFlagId, newValue).then(() => {
toast.success('Feature flag status has been updated.');
})
featureFlagsStore
.updateFlagStatus(flag.featureFlagId, newValue)
.then(() => {
toast.success('Feature flag status has been updated.');
})
.catch(() => {
flag.setIsEnabled(!newValue);
toast.error('Something went wrong, please try again.')
})
}
toast.error('Something went wrong, please try again.');
});
};
const flagIcon = flag.isSingleOption ? 'fflag-single' : 'fflag-multi' as const
const flagOwner = flag.updatedBy || flag.createdBy
const user = userStore.list.length > 0 ? userStore.list.find(u => parseInt(u.userId) === flagOwner!)?.name : flagOwner;
const flagIcon = flag.isSingleOption ? 'fflag-single' : ('fflag-multi' as const);
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 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} />
<Tooltip delay={150} title={flag.isSingleOption ? 'Single variant' : 'Multivariant'}>
<Icon name={flagIcon} size={32} />
</Tooltip>
<div className="flex flex-col gap-1" style={{ width: 300 }}>
<span className={'link'}>{flag.flagKey}</span>
{flag.description
? (
<TextEllipsis hintText={flag.description} text={flag.description} style={{ color: 'rgba(0,0,0, 0.6)'}} />
) : null}
{flag.description ? (
<TextEllipsis
hintText={flag.description}
text={flag.description}
style={{ color: 'rgba(0,0,0, 0.6)' }}
/>
) : null}
</div>
</div>
</Link>

View file

@ -3,22 +3,15 @@ import { Button, PageTitle } from 'UI'
import FFlagsSearch from "Components/FFlags/FFlagsSearch";
import { useHistory } from "react-router";
import { newFFlag, withSiteId } from 'App/routes';
import ReloadButton from "Shared/ReloadButton";
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
function FFlagsListHeader({ siteId }: { siteId: string }) {
const history = useHistory();
const { featureFlagsStore } = useStore();
const onReload = () => {
void featureFlagsStore.fetchFlags();
}
return (
<div className="flex items-center justify-between px-6">
<div className="flex items-center mr-3 gap-2">
<PageTitle title="Feature Flags" />
<ReloadButton onClick={onReload} loading={featureFlagsStore.isLoading} />
</div>
<div className="ml-auto flex items-center">
<Button variant="primary" onClick={() => history.push(withSiteId(newFFlag(), siteId))}>
@ -33,4 +26,4 @@ function FFlagsListHeader({ siteId }: { siteId: string }) {
)
}
export default observer(FFlagsListHeader);
export default FFlagsListHeader;

View file

@ -0,0 +1,117 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
import { Toggler, Loader, Button, NoContent, ItemMenu } from 'UI';
import Breadcrumb from 'Shared/Breadcrumb';
import { useHistory } from 'react-router';
import { withSiteId, fflag, fflags } from 'App/routes';
// import RolloutCondition from './Conditions';
// import { Payload } from './Helpers';
import { toast } from 'react-toastify';
import RolloutCondition from "Components/FFlags/NewFFlag/Conditions";
function FlagView({ siteId, fflagId }: { siteId: string; fflagId: string }) {
const { featureFlagsStore } = useStore();
const history = useHistory();
React.useEffect(() => {
if (fflagId) {
void featureFlagsStore.fetchFlag(parseInt(fflagId, 10));
}
}, [fflagId]);
const current = featureFlagsStore.currentFflag;
if (featureFlagsStore.isLoading) return <Loader loading={true} />;
if (!current) return <NoContent title={'No flag found'} />;
const deleteHandler = () => {
featureFlagsStore.deleteFlag(current.featureFlagId).then(() => {
toast.success('Feature flag deleted.');
history.push(withSiteId(fflags(), siteId));
});
};
const menuItems = [{ icon: 'trash', text: 'Delete', onClick: deleteHandler }];
const toggleActivity = () => {
const newValue = !current.isActive;
current.setIsEnabled(newValue);
featureFlagsStore
.updateFlagStatus(current.featureFlagId, newValue)
.then(() => {
toast.success('Feature flag status has been updated.');
})
.catch(() => {
current.setIsEnabled(!newValue);
toast.error('Something went wrong, please try again.');
});
};
return (
<div className={'w-full mx-auto mb-4'} style={{ maxWidth: 1300 }}>
<Breadcrumb
items={[
{ label: 'Feature Flags', to: withSiteId(fflags(), siteId) },
{ label: current.flagKey },
]}
/>
<div className={'w-full bg-white rounded p-4 widget-wrapper'}>
<div className={'flex items-center gap-2'}>
<div className={'text-xl font-semibold'}>{current.flagKey}</div>
<Button
className={'ml-auto'}
variant={'text-primary'}
onClick={() =>
history.push(
withSiteId(fflag(featureFlagsStore.currentFflag?.featureFlagId.toString()), siteId)
)
}
>
Edit
</Button>
<ItemMenu bold items={menuItems} />
</div>
<div className={'text-disabled-text border-b'}>
{current.description || 'There is no description for this feature flag.'}
</div>
<div className={'mt-4'}>
<label className={'font-semibold'}>Status</label>
<Toggler
checked={current.isActive}
name={'persist-flag'}
onChange={toggleActivity}
label={current.isActive ? 'Enabled' : 'Disabled'}
/>
</div>
<div className={'mt-4'}>
<label className={'font-semibold'}>Persistence</label>
<div>
{current.isPersist
? 'This flag maintains its state through successive authentication events.'
: 'This flag is not persistent.'}
</div>
</div>
{current.conditions.length > 0 ? (
<div className="mt-6 p-4 rounded bg-gray-lightest">
<label className={'font-semibold'}>Rollout Conditions</label>
{current.conditions.map((condition, index) => (
<React.Fragment key={index}>
<RolloutCondition
set={index + 1}
index={index}
conditions={condition}
removeCondition={current.removeCondition}
/>
</React.Fragment>
))}
</div>
) : null}
</div>
</div>
);
}
export default observer(FlagView);

View file

@ -1,13 +1,31 @@
import React from 'react';
import { Button } from 'UI';
import { observer } from 'mobx-react-lite';
import cn from "classnames";
import cn from 'classnames';
import { ItemMenu } from 'UI';
import { useStore } from 'App/mstore';
import { useHistory } from 'react-router';
import { toast } from 'react-toastify';
import { fflags, withSiteId } from "App/routes";
function Header({ current, onCancel, onSave, isNew }: any) {
function Header({ current, onCancel, onSave, isNew, siteId }: any) {
const { featureFlagsStore } = useStore();
const history = useHistory();
const deleteHandler = () => {
featureFlagsStore.deleteFlag(current.featureFlagId).then(() => {
toast.success('Feature flag deleted.');
history.push(withSiteId(fflags(), siteId));
});
};
const menuItems = [{ icon: 'trash', text: 'Delete', onClick: deleteHandler }];
return (
<>
<div>
<h1 className={cn('text-2xl')}>{!current.flagKey ? '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'}>
@ -17,6 +35,7 @@ function Header({ current, onCancel, onSave, isNew }: any) {
<Button variant="primary" onClick={onSave}>
Save
</Button>
{!isNew ? <ItemMenu bold items={menuItems} /> : null}
</div>
</>
);

View file

@ -4,15 +4,15 @@ import { QuestionMarkHint } from 'UI';
function Rollout() {
return (
<div className={'flex items-center gap-2'}>
Rollout <QuestionMarkHint delay={150} content={"Must add up to 100% across all variants"} />
Rollout % <QuestionMarkHint delay={150} content={"Must add up to 100% across all variants"} />
</div>
);
}
function Payload() {
return (
<div className={'flex items-center gap-2'}>
Payload <QuestionMarkHint delay={150} content={"Will be sent as an additional string"} /> <span className={"text-disabled-text text-sm"}>(Optional)</span>
<div className={'flex items-center gap-2 font-semibold'}>
Payload <QuestionMarkHint delay={150} content={"Will be sent as an additional string"} /> <span className={"text-disabled-text text-sm font-normal"}>(Optional)</span>
</div>
)
}

View file

@ -23,6 +23,9 @@ function NewFFlag({ siteId, fflagId }: { siteId: string; fflagId?: string }) {
} else {
featureFlagsStore.initNewFlag();
}
return () => {
featureFlagsStore.setCurrentFlag(null);
}
}, [fflagId]);
const current = featureFlagsStore.currentFflag;
@ -47,7 +50,6 @@ function NewFFlag({ siteId, fflagId }: { siteId: string; fflagId?: string }) {
};
const onCancel = () => {
featureFlagsStore.setCurrentFlag(null);
history.push(withSiteId(fflags(), siteId));
};
@ -90,7 +92,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 siteId={siteId} current={current} onCancel={onCancel} onSave={onSave} isNew={!fflagId} />
</div>
<div className={'w-full border-b border-light-gray my-2'} />

View file

@ -9,8 +9,9 @@ import OverviewMenu from 'Shared/OverviewMenu';
import FFlagsList from 'Components/FFlags';
import NewFFlag from 'Components/FFlags/NewFFlag';
import { Switch, Route } from 'react-router';
import { sessions, fflags, withSiteId, newFFlag, fflag, notes, bookmarks } from 'App/routes';
import { sessions, fflags, withSiteId, newFFlag, fflag, notes, fflagRead, bookmarks } from 'App/routes';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import FlagView from 'Components/FFlags/FlagView/FlagView'
// @ts-ignore
interface IProps extends RouteComponentProps {
@ -50,6 +51,9 @@ function Overview({ match: { params } }: IProps) {
<Route exact strict path={withSiteId(fflag(), siteId)}>
<NewFFlag siteId={siteId} fflagId={fflagId} />
</Route>
<Route exact strict path={withSiteId(fflagRead(), siteId)}>
<FlagView siteId={siteId} fflagId={fflagId!} />
</Route>
</Switch>
</div>
</div>

View file

@ -106,9 +106,14 @@ export default class FeatureFlag {
...data,
isSingleOption: data ? data.flagType === 'single' : true,
conditions: data?.conditions?.map(c => new Conditions(c)) || [new Conditions()],
variants: data?.flagType === 'multi' ? data?.variants?.map((v, i) => new Variant(i, v)) : [new Variant(1)],
variants: data?.flagType === 'multi' ? data?.variants?.map((v, i) => new Variant(i, v)) : [],
});
if (this.variants?.length === 0) {
this.addVariant()
this.addVariant()
this.hasChanged = false
}
makeAutoObservable(this);
}

View file

@ -86,6 +86,8 @@ export const sessions = params => queried('/sessions', params);
export const fflags = params => queried('/feature-flags', params);
export const newFFlag = () => '/feature-flags/create';
export const fflag = (id = ':fflagId', hash) => hashed(`/feature-flags/${ id }`, hash);
export const fflagRead = (id = ':fflagId', hash) => hashed(`/feature-flags/get/${ id }`, hash);
export const notes = params => queried('/notes', params);
export const bookmarks = params => queried('/bookmarks', params);
export const assist = params => queried('/assist', params);