Skip to content

Commit

Permalink
style: feature filters and compact mode (#3136)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyle-ssg authored Dec 13, 2023
1 parent 8259c53 commit f6319e5
Show file tree
Hide file tree
Showing 37 changed files with 1,058 additions and 365 deletions.
1 change: 1 addition & 0 deletions frontend/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const keywords = {
}

export default {
archivedTag: { color: '#8f8f8f', label: 'Archived' },
codeHelp: {
'CREATE_USER': (envId: string, userId: string = keywords.USER_ID) => ({
'.NET': require('./code-help/create-user/create-user-dotnet')(
Expand Down
3 changes: 2 additions & 1 deletion frontend/common/services/useTag.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PagedResponse, Res, Tag } from 'common/types/responses'
import { Req } from 'common/types/requests'
import { service } from 'common/service'
import { sortBy } from 'lodash'

export const tagService = service
.enhanceEndpoints({ addTagTypes: ['Tag'] })
Expand Down Expand Up @@ -28,7 +29,7 @@ export const tagService = service
url: `projects/${query.projectId}/tags/`,
}),
transformResponse(baseQueryReturnValue: PagedResponse<Tag>) {
return baseQueryReturnValue.results
return sortBy(baseQueryReturnValue.results, 'label')
},
}),
updateTag: builder.mutation<Res['tag'], Req['updateTag']>({
Expand Down
6 changes: 5 additions & 1 deletion frontend/common/stores/feature-list-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,10 @@ store.dispatcherIndex = Dispatcher.register(store, (payload) => {
const action = payload.action // this is our action from handleViewAction

switch (action.actionType) {
case Actions.SEARCH_FLAGS:
case Actions.SEARCH_FLAGS: {
if (action.sort) {
store.sort = action.sort
}
controller.searchFeatures(
action.search,
action.environmentId,
Expand All @@ -743,6 +746,7 @@ store.dispatcherIndex = Dispatcher.register(store, (payload) => {
action.pageSize,
)
break
}
case Actions.GET_FLAGS:
store.search = action.search || ''
if (action.sort) {
Expand Down
13 changes: 13 additions & 0 deletions frontend/common/useViewMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import flagsmith from 'flagsmith'

export type ViewMode = 'compact' | 'normal'
export function getViewMode() {
const viewMode = flagsmith.getTrait('view_mode')
if (viewMode === 'compact') {
return 'compact' as ViewMode
}
return 'default' as ViewMode
}
export function setViewMode(viewMode: ViewMode) {
return flagsmith.setTrait('view_mode', viewMode)
}
3 changes: 2 additions & 1 deletion frontend/web/components/CompareEnvironments.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Tag from './tags/Tag'
import { getProjectFlags } from 'common/services/useProjectFlag'
import { getStore } from 'common/store'
import Icon from './Icon'
import Constants from 'common/constants'

const featureNameWidth = 300

Expand Down Expand Up @@ -294,7 +295,7 @@ class CompareEnvironments extends Component {
})
}}
className='px-2 py-2 ml-2 mr-2'
tag={{ color: '#0AADDF', label: 'Archived' }}
tag={Constants.archivedTag}
/>
</Row>
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/web/components/CompareIdentities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ const CompareIdentities: FC<CompareIdentitiesType> = ({
setShowArchived(!showArchived)
}}
className='px-2 py-2 ml-2 mr-2'
tag={{ color: '#0AADDF', label: 'Archived' }}
tag={Constants.archivedTag}
/>
</Row>
}
Expand Down
93 changes: 93 additions & 0 deletions frontend/web/components/ExampleFeatureRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React, { FC } from 'react'
import { FeatureState, ProjectFlag } from 'common/types/responses'
import { useGetTagsQuery } from 'common/services/useTag'
import FeatureRow from './FeatureRow'
import { flag } from 'ionicons/icons'

type ExampleFeatureRowType = {}

const ExampleFeatureRow: FC<ExampleFeatureRowType> = ({}) => {
const flag: ProjectFlag = {
created_date: new Date().toISOString(),
default_enabled: true,
description: 'Feature description',
id: 1,
initial_value: 'Blue',
is_archived: false,
is_server_key_only: false,
multivariate_options: [],
name: 'example_feature',
num_identity_overrides: 1,
num_segment_overrides: 1,
owner_groups: [],
owners: [],
project: 1,
tags: [],
type: 'STANDARD',
uuid: '1',
}
const flag2: ProjectFlag = {
created_date: new Date().toISOString(),
default_enabled: true,
description: 'Feature description',
id: 1,
initial_value: `2`,
is_archived: false,
is_server_key_only: false,
multivariate_options: [],
name: 'example_feature2',
num_identity_overrides: 1,
num_segment_overrides: 1,
owner_groups: [],
owners: [],
project: 1,
tags: [],
type: 'STANDARD',
uuid: '1',
}
const featureState: FeatureState = {
created_at: '',
enabled: false,
environment: 1,
feature: flag.id,
feature_state_value: flag.initial_value,
id: 1,
multivariate_feature_state_values: [],
updated_at: '',
uuid: '',
}
return (
<div className='panel no-pad'>
<div className='panel-content'>
<div className='search-list'>
<FeatureRow
environmentFlags={{ 1: featureState }}
projectFlags={[flag, flag2]}
environmentId={1}
disableControls
permission={true}
projectId={'2'}
index={0}
toggleFlag={() => {}}
removeFlag={() => {}}
projectFlag={flag}
/>
<FeatureRow
environmentFlags={{ 1: featureState }}
projectFlags={[flag, flag2]}
environmentId={1}
disableControls
permission={true}
projectId={'2'}
index={1}
toggleFlag={() => {}}
removeFlag={() => {}}
projectFlag={flag}
/>
</div>
</div>
</div>
)
}

export default ExampleFeatureRow
68 changes: 50 additions & 18 deletions frontend/web/components/FeatureRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ import SegmentsIcon from './svg/SegmentsIcon'
import UsersIcon from './svg/UsersIcon' // we need this to make JSX compile
import Icon from './Icon'
import FeatureValue from './FeatureValue'
import { getViewMode } from 'common/useViewMode'
import classNames from 'classnames'
import Tag from './tags/Tag'

export const width = [200, 65, 48, 75, 450]
export const width = [200, 70, 55, 70, 450]
class TheComponent extends Component {
static contextTypes = {
router: propTypes.object.isRequired,
Expand Down Expand Up @@ -55,6 +58,9 @@ class TheComponent extends Component {
}

editFeature = (projectFlag, environmentFlag, tab) => {
if (this.props.disableControls) {
return
}
API.trackEvent(Constants.events.VIEW_FEATURE)

history.replaceState(
Expand Down Expand Up @@ -85,6 +91,7 @@ class TheComponent extends Component {

render() {
const {
disableControls,
environmentFlags,
environmentId,
permission,
Expand All @@ -102,17 +109,22 @@ class TheComponent extends Component {
const changeRequestsEnabled = Utils.changeRequestsEnabled(
environment && environment.minimum_change_request_approvals,
)

const isCompact = getViewMode() === 'compact'
if (this.props.condensed) {
return (
<Flex
onClick={() =>
onClick={() => {
if (disableControls) return
!readOnly && this.editFeature(projectFlag, environmentFlags[id])
}
}}
style={{
...(this.props.style || {}),
}}
className='flex-row'
className={
(classNames('flex-row'),
{ 'fs-small': isCompact },
this.props.className)
}
>
<div
className={`table-column ${this.props.fadeEnabled && 'faded'}`}
Expand All @@ -127,6 +139,7 @@ class TheComponent extends Component {
}`}
checked={environmentFlags[id] && environmentFlags[id].enabled}
onChange={() => {
if (disableControls) return
if (changeRequestsEnabled) {
this.editFeature(projectFlag, environmentFlags[id])
return
Expand Down Expand Up @@ -169,9 +182,16 @@ class TheComponent extends Component {
}
return (
<Row
className={`list-item ${readOnly ? '' : 'clickable'} ${
this.props.widget ? 'py-1' : 'py-2'
}`}
className={classNames(
`list-item ${readOnly ? '' : 'clickable'} ${
isCompact
? 'py-0 list-item-xs fs-small'
: this.props.widget
? 'py-1'
: 'py-2'
}`,
this.props.className,
)}
key={id}
space
data-test={`feature-item-${this.props.index}`}
Expand Down Expand Up @@ -203,9 +223,9 @@ class TheComponent extends Component {
</span>
}
>
{`Created ${moment(created_date).format(
'Do MMM YYYY HH:mma',
)}`}
{`${isCompact && `${description}<br/>`}Created ${moment(
created_date,
).format('Do MMM YYYY HH:mma')}`}
</Tooltip>
) : (
name
Expand Down Expand Up @@ -272,13 +292,16 @@ class TheComponent extends Component {
}
</Tooltip>
)}
{projectFlag.is_archived && (
<Tag className='chip--xs' tag={Constants.archivedTag} />
)}
<TagValues
inline
projectId={`${projectId}`}
value={projectFlag.tags}
/>
</Row>
{description && (
{description && !isCompact && (
<div
className='list-item-subtitle mt-1'
style={{ lineHeight: '20px', width: width[4] }}
Expand Down Expand Up @@ -335,7 +358,7 @@ class TheComponent extends Component {
/>
</div>
<div
className='table-column'
className='table-column text-right'
style={{ width: width[2] }}
onClick={(e) => {
e.stopPropagation()
Expand All @@ -348,10 +371,12 @@ class TheComponent extends Component {
title={
<div
onClick={() => {
if (disableControls) return
this.context.router.history.push(
`/project/${projectId}/environment/${environmentId}/audit-log?env=${environment.id}&search=${projectFlag.name}`,
)
}}
className='text-center'
data-test={`feature-history-${this.props.index}`}
>
<Icon name='clock' width={24} fill='#9DA4AE' />
Expand All @@ -363,7 +388,7 @@ class TheComponent extends Component {
)}
</div>
<div
className='table-column'
className='table-column text-right'
style={{ width: width[3] }}
onClick={(e) => {
e.stopPropagation()
Expand All @@ -386,15 +411,22 @@ class TheComponent extends Component {
disabled={
!removeFeaturePermission || readOnly || isProtected
}
onClick={() =>
onClick={() => {
if (disableControls) return
this.confirmRemove(projectFlag, () => {
removeFlag(projectId, projectFlag)
})
}
className='btn btn-with-icon'
}}
className={classNames('btn btn-with-icon', {
'btn-sm': isCompact,
})}
data-test={`remove-feature-btn-${this.props.index}`}
>
<Icon name='trash-2' width={20} fill='#656D7B' />
<Icon
name='trash-2'
width={isCompact ? 16 : 20}
fill='#656D7B'
/>
</Button>
}
>
Expand Down
14 changes: 11 additions & 3 deletions frontend/web/components/FeatureValue.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { FC } from 'react'
import { FlagsmithValue } from 'common/types/responses'
import Format from 'common/utils/format'
import Utils from 'common/utils/utils' // we need this to make JSX compile
import Utils from 'common/utils/utils'
import { getViewMode } from 'common/useViewMode'
import classNames from 'classnames' // we need this to make JSX compile

type FeatureValueType = {
value: FlagsmithValue
Expand All @@ -19,15 +21,21 @@ const FeatureValue: FC<FeatureValueType> = (props) => {
if (type === 'string' && props.value === '' && !props.includeEmpty) {
return null
}
const isCompact = getViewMode() === 'compact'
return (
<span
className={`chip ${props.className || ''}`}
className={classNames(`chip ${props.className || ''}`, {
'chip--sm justify-content-start d-inline': isCompact,
})}
onClick={props.onClick}
data-test={props['data-test']}
>
{type == 'string' && <span className='quot'>"</span>}
<span className='feature-value'>
{Format.truncateText(`${Utils.getTypedValue(props.value)}`, 20)}
{Format.truncateText(
`${Utils.getTypedValue(props.value)}`,
isCompact ? 24 : 20,
)}
</span>
{type == 'string' && <span className='quot'>"</span>}
</span>
Expand Down
Loading

3 comments on commit f6319e5

@vercel
Copy link

@vercel vercel bot commented on f6319e5 Dec 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

docs – ./docs

docs-flagsmith.vercel.app
docs-git-main-flagsmith.vercel.app
docs.bullet-train.io
docs.flagsmith.com

@vercel
Copy link

@vercel vercel bot commented on f6319e5 Dec 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on f6319e5 Dec 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.