Skip to content

Commit

Permalink
feat: Flag group owners (#3112)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyle-ssg authored Dec 7, 2023
1 parent 84f0df3 commit b0a00d0
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 21 deletions.
18 changes: 18 additions & 0 deletions frontend/common/services/useProjectFlag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ export const projectFlagService = service
.enhanceEndpoints({ addTagTypes: ['ProjectFlag'] })
.injectEndpoints({
endpoints: (builder) => ({
getProjectFlag: builder.query<Res['projectFlag'], Req['getProjectFlag']>({
providesTags: (res) => [{ id: res?.id, type: 'ProjectFlag' }],
query: (query: Req['getProjectFlag']) => ({
url: `projects/${query.project}/features/${query.id}/`,
}),
}),
getProjectFlags: builder.query<
Res['projectFlags'],
Req['getProjectFlags']
Expand Down Expand Up @@ -62,9 +68,21 @@ export async function getProjectFlags(
projectFlagService.endpoints.getProjectFlags.initiate(data, options),
)
}
export async function getProjectFlag(
store: any,
data: Req['getProjectFlag'],
options?: Parameters<
typeof projectFlagService.endpoints.getProjectFlag.initiate
>[1],
) {
return store.dispatch(
projectFlagService.endpoints.getProjectFlag.initiate(data, options),
)
}
// END OF FUNCTION_EXPORTS

export const {
useGetProjectFlagQuery,
useGetProjectFlagsQuery,
// END OF EXPORTS
} = projectFlagService
Expand Down
1 change: 1 addition & 0 deletions frontend/common/types/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export type Req = {
user: string
}
getProjectFlags: { project: string }
getProjectFlag: { project: string; id: string }
getRolesPermissionUsers: { organisation_id: string; role_id: string }
deleteRolesPermissionUsers: {
organisation_id: string
Expand Down
12 changes: 7 additions & 5 deletions frontend/common/types/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ export type LaunchDarklyProjectImport = {
updated_at: string
completed_at: string
status: {
requested_environment_count: number
requested_flag_count: number
result: string || null
error_message: string || null
},
requested_environment_count: number
requested_flag_count: number
result: string | null
error_message: string | null
}
project: number
}

Expand Down Expand Up @@ -257,6 +257,7 @@ export type ProjectFlag = {
num_identity_overrides: number | null
num_segment_overrides: number | null
owners: User[]
owner_groups: UserGroupSummary[]
project: number
tags: number[]
type: string
Expand Down Expand Up @@ -371,6 +372,7 @@ export type Res = {
rolePermission: { id: string }

projectFlags: PagedResponse<ProjectFlag>
projectFlag: ProjectFlag
identityFeatureStates: IdentityFeatureState[]
rolesPermissionUsers: RolePermissionUser
rolePermissionGroup: { id: string }
Expand Down
122 changes: 122 additions & 0 deletions frontend/web/components/FlagOwnerGroups.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React, { Component } from 'react'
import data from 'common/data/base/_data'
import UserSelect from './UserSelect'
import ConfigProvider from 'common/providers/ConfigProvider'
import Icon from './Icon'
import { close } from 'ionicons/icons'
import { IonIcon } from '@ionic/react'
import GroupSelect from './GroupSelect'
import { getProjectFlag } from 'common/services/useProjectFlag'
import { getStore } from 'common/store'

class TheComponent extends Component {
state = {}
componentDidMount() {
this.getData()
}

getData = () => {
getProjectFlag(getStore(), {
id: this.props.id,
project: this.props.projectId,
}).then((res) => {
const groupOwners = (res.data.group_owners || []).map((v) => v.id)
this.setState({ groupOwners })
})
}

addOwner = (id) => {
this.setState({ groupOwners: (this.state.groupOwners || []).concat(id) })
data.post(
`${Project.api}projects/${this.props.projectId}/features/${this.props.id}/add-group-owners/`,
{
group_ids: [id],
},
)
}

removeOwner = (id) => {
this.setState({
groupOwners: (this.state.groupOwners || []).filter((v) => v !== id),
})
data.post(
`${Project.api}projects/${this.props.projectId}/features/${this.props.id}/remove-group-owners/`,
{
group_ids: [id],
},
)
}

getGroupOwners = (users, groupOwners) =>
users ? users.filter((v) => groupOwners.includes(v.id)) : []

render() {
const hasPermission = Utils.getPlansPermission('FLAG_OWNERS')

return (
<OrganisationProvider>
{({ groups }) => {
const ownerUsers = this.getGroupOwners(
groups,
this.state.groupOwners || [],
)
const res = (
<div>
<Row
className='clickable'
onClick={() => {
if (hasPermission) this.setState({ showUsers: true })
}}
>
<label className='cols-sm-2 control-label'>
Assigned groups{' '}
<Icon name='setting' width={20} fill={'#656D7B'} />
</label>
</Row>
<Row style={{ rowGap: '12px' }}>
{hasPermission &&
ownerUsers.map((u) => (
<Row
key={u.id}
onClick={() => this.removeOwner(u.id)}
className='chip mr-2'
>
<span className='font-weight-bold'>{u.name}</span>
<span className='chip-icon ion'>
<IonIcon icon={close} />
</span>
</Row>
))}
{!ownerUsers.length && (
<div>This flag has no assigned groups</div>
)}
</Row>
<GroupSelect
groups={groups}
value={this.state.groupOwners}
isOpen={this.state.showUsers}
size={null}
onAdd={this.addOwner}
onRemove={this.removeOwner}
onToggle={() =>
this.setState({ showUsers: !this.state.showUsers })
}
/>
</div>
)
return hasPermission ? (
res
) : (
<div>
{res}
The add flag assignees feature is available with our{' '}
<strong>Scale-up</strong> plan.
</div>
)
}}
</OrganisationProvider>
)
}
}

export default ConfigProvider(TheComponent)
24 changes: 14 additions & 10 deletions frontend/web/components/FlagOwners.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import ConfigProvider from 'common/providers/ConfigProvider'
import Icon from './Icon'
import { close } from 'ionicons/icons'
import { IonIcon } from '@ionic/react'
import { getProjectFlag } from 'common/services/useProjectFlag'
import { getStore } from 'common/store'

class TheComponent extends Component {
state = {}
Expand All @@ -13,14 +15,13 @@ class TheComponent extends Component {
}

getData = () => {
data
.get(
`${Project.api}projects/${this.props.projectId}/features/${this.props.id}/`,
)
.then((res) => {
const owners = (res.owners || []).map((v) => v.id)
this.setState({ owners })
})
getProjectFlag(getStore(), {
id: this.props.id,
project: this.props.projectId,
}).then((res) => {
const owners = (res.data.owners || []).map((v) => v.id)
this.setState({ owners })
})
}

addOwner = (id) => {
Expand Down Expand Up @@ -62,7 +63,8 @@ class TheComponent extends Component {
}}
>
<label className='cols-sm-2 control-label'>
Assignees <Icon name='setting' width={20} fill={'#656D7B'} />
Assigned users{' '}
<Icon name='setting' width={20} fill={'#656D7B'} />
</label>
</Row>
<Row style={{ rowGap: '12px' }}>
Expand All @@ -81,7 +83,9 @@ class TheComponent extends Component {
</span>
</Row>
))}
{!ownerUsers.length && <div>This flag has no assignees</div>}
{!ownerUsers.length && (
<div>This flag has no assigned users</div>
)}
</Row>

<UserSelect
Expand Down
21 changes: 15 additions & 6 deletions frontend/web/components/modals/CreateFlag.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { setInterceptClose } from './base/ModalDefault'
import Icon from 'components/Icon'
import ModalHR from './ModalHR'
import FeatureValue from 'components/FeatureValue'
import FlagOwnerGroups from 'components/FlagOwnerGroups'

const CreateFlag = class extends Component {
static displayName = 'CreateFlag'
Expand Down Expand Up @@ -518,12 +519,20 @@ const CreateFlag = class extends Component {
>
{({ permission: projectAdmin }) =>
projectAdmin && (
<FormGroup className='mb-5 setting'>
<FlagOwners
projectId={this.props.projectId}
id={projectFlag.id}
/>
</FormGroup>
<>
<FormGroup className='mb-5 setting'>
<FlagOwners
projectId={this.props.projectId}
id={projectFlag.id}
/>
</FormGroup>
<FormGroup className='mb-5 setting'>
<FlagOwnerGroups
projectId={this.props.projectId}
id={projectFlag.id}
/>
</FormGroup>
</>
)
}
</Permission>
Expand Down

3 comments on commit b0a00d0

@vercel
Copy link

@vercel vercel bot commented on b0a00d0 Dec 7, 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 b0a00d0 Dec 7, 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 b0a00d0 Dec 7, 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.