Skip to content

Commit

Permalink
feat: Enabled state filter (Frontend) (#3542)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyle-ssg committed Apr 2, 2024
1 parent 87a6901 commit 741320e
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 4 deletions.
8 changes: 7 additions & 1 deletion frontend/common/useSearchThrottle.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import useThrottle from './useThrottle'

export default function useSearchThrottle(
defaultValue?: string,
cb?: () => void,
) {
const firstRender = useRef(true)
const [search, setSearch] = useState(defaultValue || '')
const [searchInput, setSearchInput] = useState(search)
const searchItems = useThrottle((search: string) => {
setSearch(search)
cb?.()
}, 100)
useEffect(() => {
if (firstRender.current && !searchInput) {
firstRender.current = false
return
}
firstRender.current = false
searchItems(searchInput)
//eslint-disable-next-line
}, [searchInput]);
Expand Down
35 changes: 34 additions & 1 deletion frontend/web/components/pages/FeaturesPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import TableTagFilter from 'components/tables/TableTagFilter'
import { setViewMode } from 'common/useViewMode'
import TableFilterOptions from 'components/tables/TableFilterOptions'
import { getViewMode } from 'common/useViewMode'
import { TagStrategy } from 'common/types/responses'
import EnvironmentDocumentCodeHelp from 'components/EnvironmentDocumentCodeHelp'
import TableValueFilter from 'components/tables/TableValueFilter'

const FeaturesPage = class extends Component {
static displayName = 'FeaturesPage'
Expand All @@ -32,12 +32,14 @@ const FeaturesPage = class extends Component {
constructor(props, context) {
super(props, context)
this.state = {
is_enabled: null,
loadedOnce: false,
search: null,
showArchived: false,
sort: { label: 'Name', sortBy: 'name', sortOrder: 'asc' },
tag_strategy: 'INTERSECTION',
tags: [],
value_search: null,
}
ES6Component(this)
getTags(getStore(), {
Expand Down Expand Up @@ -106,11 +108,14 @@ const FeaturesPage = class extends Component {

getFilter = () => ({
is_archived: this.state.showArchived,
is_enabled:
this.state.is_enabled === null ? undefined : this.state.is_enabled,
tag_strategy: this.state.tag_strategy,
tags:
!this.state.tags || !this.state.tags.length
? undefined
: this.state.tags.join(','),
value_search: this.state.value_search ? this.state.value_search : undefined,
})

onSave = (isCreate) => {
Expand Down Expand Up @@ -165,6 +170,9 @@ const FeaturesPage = class extends Component {
render() {
const { environmentId, projectId } = this.props.match.params
const readOnly = Utils.getFlagsmithHasFeature('read_only_mode')
const enabledStateFilter = Utils.getFlagsmithHasFeature(
'feature_enabled_state_filter',
)
const environment = ProjectStore.getEnvironment(environmentId)
return (
<div
Expand Down Expand Up @@ -381,6 +389,31 @@ const FeaturesPage = class extends Component {
)
}}
/>
{enabledStateFilter && (
<TableValueFilter
title={'State'}
className={'me-4'}
projectId={projectId}
useLocalStorage
value={{
enabled: this.state.is_enabled,
valueSearch:
this.state.value_search,
}}
onChange={({
enabled,
valueSearch,
}) => {
this.setState(
{
is_enabled: enabled,
value_search: valueSearch,
},
this.filter,
)
}}
/>
)}
<TableFilterOptions
title={'View'}
className={'me-4'}
Expand Down
32 changes: 32 additions & 0 deletions frontend/web/components/pages/UserPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import classNames from 'classnames'
import IdentifierString from 'components/IdentifierString'
import Button from 'components/base/forms/Button'
import { removeUserOverride } from 'components/RemoveUserOverride'
import TableValueFilter from 'components/tables/TableValueFilter'
const width = [200, 48, 78]
const valuesEqual = (actualValue, flagValue) => {
const nullFalseyA =
Expand All @@ -46,20 +47,25 @@ const UserPage = class extends Component {
constructor(props, context) {
super(props, context)
this.state = {
is_enabled: null,
preselect: Utils.fromParam().flag,
showArchived: false,
tag_strategy: 'INTERSECTION',
tags: [],
value_search: null,
}
}

getFilter = () => ({
is_archived: this.state.showArchived,
is_enabled:
this.state.is_enabled === null ? undefined : this.state.is_enabled,
tag_strategy: this.state.tag_strategy,
tags:
!this.state.tags || !this.state.tags.length
? undefined
: this.state.tags.join(','),
value_search: this.state.value_search ? this.state.value_search : undefined,
})

componentDidMount() {
Expand Down Expand Up @@ -262,6 +268,9 @@ const UserPage = class extends Component {
render() {
const { actualFlags } = this.state
const { environmentId, projectId } = this.props.match.params
const enabledStateFilter = Utils.getFlagsmithHasFeature(
'feature_enabled_state_filter',
)

const preventAddTrait = !AccountStore.getOrganisation().persist_trait_data
return (
Expand Down Expand Up @@ -483,6 +492,29 @@ const UserPage = class extends Component {
)
}}
/>
{enabledStateFilter && (
<TableValueFilter
className='me-4'
useLocalStorage
value={{
enabled: this.state.is_enabled,
valueSearch:
this.state.value_search,
}}
onChange={({
enabled,
valueSearch,
}) => {
this.setState(
{
is_enabled: enabled,
value_search: valueSearch,
},
this.filter,
)
}}
/>
)}
<TableFilterOptions
title={'View'}
className={'me-4'}
Expand Down
3 changes: 1 addition & 2 deletions frontend/web/components/tables/TableFilterOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import { IonIcon } from '@ionic/react'
import { caretDown } from 'ionicons/icons'
import classNames from 'classnames'
import TableFilterItem from './TableFilterItem'
import { ViewMode } from 'common/useViewMode'

type TableFilterType = {
title: string
dropdownTitle?: ReactNode | string
className?: string
options: { label: string; value: string }[]
onChange: (value: ViewMode) => void | Promise<void>
onChange: (value: any) => void | Promise<void>
value: string
}

Expand Down
144 changes: 144 additions & 0 deletions frontend/web/components/tables/TableValueFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import React, { FC, useEffect, useMemo, useRef, useState } from 'react'
import TableFilter from './TableFilter'
import Input from 'components/base/forms/Input'
import Utils from 'common/utils/utils'
import { useGetTagsQuery } from 'common/services/useTag'
import Tag from 'components/tags/Tag'
import TableFilterItem from './TableFilterItem'
import Constants from 'common/constants'
import { TagStrategy } from 'common/types/responses'
import { AsyncStorage } from 'polyfill-react-native'
import InputGroup from 'components/base/forms/InputGroup'
import useSearchThrottle from 'common/useSearchThrottle'

type TableFilterType = {
value: {
enabled: boolean | null
valueSearch: string | null
}
enabled: boolean | null
isLoading: boolean
onChange: (value: TableFilterType['value']) => void
className?: string
useLocalStorage?: boolean
projectId: string
}

const enabledOptions = [
{
label: 'Any',
value: null,
},
{
label: 'Enabled',
value: true,
},
{
label: 'Disabled',
value: false,
},
]
const TableTagFilter: FC<TableFilterType> = ({
className,
isLoading,
onChange,
projectId,
useLocalStorage,
value,
}) => {
const checkedLocalStorage = useRef(false)
const { searchInput, setSearchInput } = useSearchThrottle(
value.valueSearch || '',
() => {
onChange({
enabled: value.enabled,
valueSearch: searchInput,
})
},
)
useEffect(() => {
if (checkedLocalStorage.current && useLocalStorage) {
AsyncStorage.setItem(`${projectId}-value`, JSON.stringify(value))
}
}, [value, projectId, useLocalStorage])
useEffect(() => {
const checkLocalStorage = async function () {
if (useLocalStorage && !checkedLocalStorage.current) {
checkedLocalStorage.current = true
const storedValue = await AsyncStorage.getItem(`${projectId}-value`)
if (storedValue) {
try {
const storedValueObject = JSON.parse(storedValue)
onChange(storedValueObject)
} catch (e) {}
}
}
}
checkLocalStorage()
}, [useLocalStorage, projectId])
return (
<div className={isLoading ? 'disabled' : ''}>
<TableFilter
className={className}
hideTitle
title={
<Row>
Value{' '}
{(value.enabled !== null || !!value.valueSearch) && (
<span className='mx-1 unread d-inline'>{1}</span>
)}
</Row>
}
>
<div className='inline-modal__list d-flex flex-column mx-0 py-0'>
<div className='px-2 mt-2'>
<InputGroup
title='Enabled State'
className='mt-2'
component={
<Select
size='select-xxsm'
styles={{
control: (base) => ({
...base,
'&:hover': { borderColor: '$bt-brand-secondary' },
border: '1px solid $bt-brand-secondary',
height: 18,
}),
}}
onChange={(e: (typeof enabledOptions)[number]) => {
onChange({
enabled: e.value,
valueSearch: value.valueSearch,
})
}}
value={enabledOptions.find((v) => v.value === value.enabled)}
options={enabledOptions}
/>
}
/>
<InputGroup
title='Feature Value'
autoFocus
onChange={(e: InputEvent) => {
setSearchInput(Utils.safeParseEventValue(e))
}}
tooltip='This will filter your features based on the remote configuration value you define.'
inputProps={{ style: { height: 60 } }}
value={searchInput}
textarea
rows={2}
className='full-width mt-2'
type='text'
size='xSmall'
placeholder='Enter a feature value'
search
/>
</div>
</div>
</TableFilter>
</div>
)
}

export default TableTagFilter

0 comments on commit 741320e

Please sign in to comment.