Skip to content

Commit

Permalink
feat: Clone identities (FE) (#3725)
Browse files Browse the repository at this point in the history
  • Loading branch information
novakzaballa committed Apr 30, 2024
1 parent 2417f57 commit 084d775
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 26 deletions.
49 changes: 40 additions & 9 deletions frontend/common/services/useIdentityFeatureState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,29 @@ export const identityFeatureStateService = service
.enhanceEndpoints({ addTagTypes: ['IdentityFeatureState'] })
.injectEndpoints({
endpoints: (builder) => ({
getIdentityFeatureStates: builder.query<
createCloneIdentityFeatureStates: builder.mutation<
Res['cloneidentityFeatureStates'],
Req['createCloneIdentityFeatureStates']
>({
invalidatesTags: [{ type: 'IdentityFeatureState' }],
query: (query: Req['createCloneIdentityFeatureStates']) => ({
body: query.body,
method: 'POST',
url: `environments/${
query.environment_id
}/${Utils.getIdentitiesEndpoint()}/${
query.identity_id
}/${Utils.getFeatureStatesEndpoint()}/clone-from-given-identity/`,
}),
}),
getIdentityFeatureStatesAll: builder.query<
Res['identityFeatureStates'],
Req['getIdentityFeatureStates']
Req['getIdentityFeatureStatesAll']
>({
providesTags: (res, _, req) => [
{ id: req.user, type: 'IdentityFeatureState' },
],
query: (query: Req['getIdentityFeatureStates']) => ({
query: (query: Req['getIdentityFeatureStatesAll']) => ({
url: `environments/${
query.environment
}/${Utils.getIdentitiesEndpoint()}/${
Expand All @@ -26,15 +41,30 @@ export const identityFeatureStateService = service
}),
})

export async function getIdentityFeatureStates(
export async function getIdentityFeatureStateAll(
store: any,
data: Req['getIdentityFeatureStatesAll'],
options?: Parameters<
typeof identityFeatureStateService.endpoints.getIdentityFeatureStatesAll.initiate
>[1],
) {
return store.dispatch(
identityFeatureStateService.endpoints.getIdentityFeatureStatesAll.initiate(
data,
options,
),
)
}

export async function createIdentityFeatureStates(
store: any,
data: Req['getIdentityFeatureStates'],
data: Req['createCloneIdentityFeatureStates'],
options?: Parameters<
typeof identityFeatureStateService.endpoints.getIdentityFeatureStates.initiate
typeof identityFeatureStateService.endpoints.createCloneIdentityFeatureStates.initiate
>[1],
) {
return store.dispatch(
identityFeatureStateService.endpoints.getIdentityFeatureStates.initiate(
identityFeatureStateService.endpoints.createCloneIdentityFeatureStates.initiate(
data,
options,
),
Expand All @@ -43,12 +73,13 @@ export async function getIdentityFeatureStates(
// END OF FUNCTION_EXPORTS

export const {
useGetIdentityFeatureStatesQuery,
useCreateCloneIdentityFeatureStatesMutation,
useGetIdentityFeatureStatesAllQuery,
// END OF EXPORTS
} = identityFeatureStateService

/* Usage examples:
const { data, isLoading } = useGetIdentityFeatureStatesQuery({ id: 2 }, {}) //get hook
const [createIdentityFeatureStates, { isLoading, data, isSuccess }] = useCreateIdentityFeatureStatesMutation() //create hook
identityFeatureStateService.endpoints.getIdentityFeatureStates.select({id: 2})(store.getState()) //access data from any function
identityFeatureStateService.endpoints.getIdentityFeatureStatesAll.select({id: 2})(store.getState()) //access data from any function
*/
15 changes: 10 additions & 5 deletions frontend/common/stores/organisation-store.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Constants from 'common/constants'
import { projectService } from "common/services/useProject";
import { getStore } from "common/store";
import { projectService } from 'common/services/useProject'
import { getStore } from 'common/store'
import sortBy from 'lodash/sortBy'

const Dispatcher = require('../dispatcher/dispatcher')
Expand Down Expand Up @@ -30,8 +30,13 @@ const controller = {
API.trackEvent(Constants.events.CREATE_FIRST_PROJECT)
}
API.trackEvent(Constants.events.CREATE_PROJECT)
const defaultEnvironmentNames = Utils.getFlagsmithHasFeature('default_environment_names_for_new_project')
? JSON.parse(Utils.getFlagsmithValue('default_environment_names_for_new_project')) : ['Development', 'Production']
const defaultEnvironmentNames = Utils.getFlagsmithHasFeature(
'default_environment_names_for_new_project',
)
? JSON.parse(
Utils.getFlagsmithValue('default_environment_names_for_new_project'),
)
: ['Development', 'Production']
data
.post(`${Project.api}projects/`, { name, organisation: store.id })
.then((project) => {
Expand All @@ -43,7 +48,7 @@ const controller = {
project: project.id,
})
.then((res) => createSampleUser(res, envName, project))
})
}),
).then((res) => {
project.environments = res
store.model.projects = store.model.projects.concat(project)
Expand Down
9 changes: 8 additions & 1 deletion frontend/common/types/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export type Req = {
updateRolePermission: Req['createRolePermission'] & { id: number }
deleteRolePermission: { organisation_id: number; role_id: number }

getIdentityFeatureStates: {
getIdentityFeatureStatesAll: {
environment: string
user: string
}
Expand Down Expand Up @@ -380,6 +380,13 @@ export type Req = {
usersToAddAdmin: number[] | null
}
getUserGroupPermission: { project_id: string }
createCloneIdentityFeatureStates: {
environment_id: string
identity_id: string
body: {
source_identity_id: string
}
}
updateGroup: Req['createGroup'] & {
orgId: string
data: UserGroup
Expand Down
5 changes: 4 additions & 1 deletion frontend/common/types/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ export type IdentityFeatureState = {
enabled: boolean
feature_state_value: FlagsmithValue
segment: null
overridden_by: string | null
multivariate_feature_state_values?: {
multivariate_feature_option: {
value: number
Expand Down Expand Up @@ -670,7 +671,7 @@ export type Res = {
rolePermission: PagedResponse<UserPermission>
projectFlags: PagedResponse<ProjectFlag>
projectFlag: ProjectFlag
identityFeatureStates: IdentityFeatureState[]
identityFeatureStatesAll: IdentityFeatureState[]
createRolesPermissionUsers: RolePermissionUser
rolesPermissionUsers: PagedResponse<RolePermissionUser>
createRolePermissionGroup: RolePermissionGroup
Expand Down Expand Up @@ -707,5 +708,7 @@ export type Res = {
featureImports: PagedResponse<FeatureImport>
serversideEnvironmentKeys: APIKey[]
userGroupPermissions: GroupPermission[]
identityFeatureStates: PagedResponse<FeatureState>
cloneidentityFeatureStates: IdentityFeatureState
// END OF TYPES
}
5 changes: 4 additions & 1 deletion frontend/web/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,10 @@ const App = class extends Component {
fill='#9DA4AE'
/>
</span>
Organisation <strong>{AccountStore.getOrganisation()?.name}</strong>
Organisation{' '}
<strong>
{AccountStore.getOrganisation()?.name}
</strong>
</NavLink>
</Row>
<Row>
Expand Down
79 changes: 75 additions & 4 deletions frontend/web/components/CompareIdentities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import React, { FC, useEffect, useMemo, useState } from 'react'
import IdentitySelect, { IdentitySelectType } from './IdentitySelect'
import Utils from 'common/utils/utils'
import EnvironmentSelect from './EnvironmentSelect'
import { useGetIdentityFeatureStatesQuery } from 'common/services/useIdentityFeatureState'
import {
useGetIdentityFeatureStatesAllQuery,
useCreateCloneIdentityFeatureStatesMutation,
} from 'common/services/useIdentityFeatureState'
import { useGetProjectFlagsQuery } from 'common/services/useProjectFlag'
import Tag from './tags/Tag'
import PanelSearch from './PanelSearch'
Expand All @@ -17,6 +20,8 @@ import Button from './base/forms/Button'
import ProjectStore from 'common/stores/project-store'
import SegmentOverridesIcon from './SegmentOverridesIcon'
import IdentityOverridesIcon from './IdentityOverridesIcon'
import Tooltip from './Tooltip'
import PageTitle from './PageTitle'

type CompareIdentitiesType = {
projectId: string
Expand Down Expand Up @@ -66,14 +71,16 @@ const CompareIdentities: FC<CompareIdentitiesType> = ({
permission: Utils.getViewIdentitiesPermission(),
})

const { data: leftUser } = useGetIdentityFeatureStatesQuery(
const { data: leftUser } = useGetIdentityFeatureStatesAllQuery(
{ environment: environmentId, user: `${leftId?.value}` },
{ skip: !leftId },
)
const { data: rightUser } = useGetIdentityFeatureStatesQuery(
const { data: rightUser } = useGetIdentityFeatureStatesAllQuery(
{ environment: environmentId, user: `${rightId?.value}` },
{ skip: !rightId },
)
const [createCloneIdentityFeatureStates] =
useCreateCloneIdentityFeatureStatesMutation()

useEffect(() => {
// Clear users whenever environment or project is changed
Expand Down Expand Up @@ -120,6 +127,38 @@ const CompareIdentities: FC<CompareIdentitiesType> = ({
)
}

const cloneIdentityValues = (
leftIdentityName: string,
rightIdentityName: string,
leftIdentityId: string,
rightIdentityId: string,
environmentId: string,
) => {
return openConfirm({
body: (
<div>
{'Cloning '} <strong>{leftIdentityName}</strong>{' '}
{'will copy any Identity Overrides in '}
<strong>{`${rightIdentityName}.`}</strong> {'Are you sure?'}
</div>
),
destructive: true,
onYes: () => {
createCloneIdentityFeatureStates({
body: {
source_identity_id: leftIdentityId,
},
environment_id: environmentId,
identity_id: rightIdentityId,
}).then(() => {
toast('Clonation Completed!')
})
},
title: 'Clone Identity',
yesText: 'Confirm',
})
}

return (
<div>
<div className='col-md-8'>
Expand Down Expand Up @@ -179,9 +218,41 @@ const CompareIdentities: FC<CompareIdentitiesType> = ({

{isReady && (
<>
<PageTitle
title={'Changed Flags'}
className='mt-3'
cta={
<>
{Utils.getFlagsmithHasFeature('clone_identities') && (
<>
<Tooltip
title={
<Button
disabled={!leftId || !rightId || !environmentId}
onClick={() => {
cloneIdentityValues(
leftId?.label,
rightId?.label,
leftId?.value,
rightId?.value,
environmentId,
)
}}
className='ms-2 me-2'
>
{'Clone Features states'}
</Button>
}
>
{`Clone the Features states from ${leftId?.label} to ${rightId?.label}`}
</Tooltip>
</>
)}
</>
}
></PageTitle>
<PanelSearch
className='no-pad mt-4'
title={'Changed Flags'}
searchPanel={
<Row className='mb-2'>
<Tag
Expand Down
2 changes: 1 addition & 1 deletion frontend/web/components/EditPermissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ import Panel from './base/grid/Panel'
import InputGroup from './base/forms/InputGroup'
import classNames from 'classnames'
import OrganisationProvider from 'common/providers/OrganisationProvider'
import { useHasPermission } from 'common/providers/Permission';
import { useHasPermission } from 'common/providers/Permission'
const Project = require('common/project')

type EditPermissionModalType = {
Expand Down
4 changes: 1 addition & 3 deletions frontend/web/components/FeatureRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,7 @@ class TheComponent extends Component {
<Tag className='chip--xs' tag={Constants.archivedTag} />
)}
</TagValues>
{!!isCompact && (
<StaleFlagWarning projectFlag={projectFlag} />
)}
{!!isCompact && <StaleFlagWarning projectFlag={projectFlag} />}
</Row>
{description && !isCompact && (
<div
Expand Down
2 changes: 1 addition & 1 deletion frontend/web/components/RolePermissionsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
useGetRoleProjectPermissionsQuery,
} from 'common/services/useRolePermission'
import { PermissionLevel } from 'common/types/requests'
import { Role, User, UserGroup, UserGroupSummary } from "common/types/responses";
import { Role, User, UserGroup, UserGroupSummary } from 'common/types/responses'
import PanelSearch from './PanelSearch'
import PermissionsSummaryList from './PermissionsSummaryList'

Expand Down

0 comments on commit 084d775

Please sign in to comment.