fix(ui): texts for feature flags, colors and stuff
This commit is contained in:
parent
643cd07bea
commit
b41dd03276
10 changed files with 203 additions and 42 deletions
|
|
@ -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 />} />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
117
frontend/app/components/FFlags/FlagView/FlagView.tsx
Normal file
117
frontend/app/components/FFlags/FlagView/FlagView.tsx
Normal 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);
|
||||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'} />
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue