Skip to content

Commit

Permalink
feat: Compare identities (#2616)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyle-ssg committed Aug 10, 2023
1 parent 8d61376 commit aafce13
Show file tree
Hide file tree
Showing 39 changed files with 668 additions and 166 deletions.
4 changes: 2 additions & 2 deletions frontend/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ module.exports = {
'CodeHelp': true,
'Column': true,
'Cookies': true,
'Dispatcher': true,
'DYNATRACE_URL': true,
'dtrum': true,
'Dispatcher': true,
'E2E': true,
'ES6Component': true,
'FB': true,
Expand All @@ -52,6 +51,7 @@ module.exports = {
'OptionalNumber': true,
'OptionalObject': true,
'OptionalString': true,
'dtrum': true,
'OrganisationProvider': true,
'OrganisationSelect': true,
'Paging': true,
Expand Down
3 changes: 2 additions & 1 deletion frontend/common/services/useIdentity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,13 @@ export const identityService = service
page_size = 10,
pageType,
pages,
q,
search,
} = baseQuery
let url = `${getIdentityEndpoint(
environmentId,
isEdge,
)}/?q=${encodeURIComponent(search || '')}&page_size=${page_size}`
)}/?q=${encodeURIComponent(search || q || '')}&page_size=${page_size}`
let last_evaluated_key = null
if (!isEdge) {
url += `&page=${page}`
Expand Down
54 changes: 54 additions & 0 deletions frontend/common/services/useIdentityFeatureState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Res } from 'common/types/responses'
import { Req } from 'common/types/requests'
import { service } from 'common/service'
import Utils from 'common/utils/utils'

export const identityFeatureStateService = service
.enhanceEndpoints({ addTagTypes: ['IdentityFeatureState'] })
.injectEndpoints({
endpoints: (builder) => ({
getIdentityFeatureStates: builder.query<
Res['identityFeatureStates'],
Req['getIdentityFeatureStates']
>({
providesTags: (res, _, req) => [
{ id: req.user, type: 'IdentityFeatureState' },
],
query: (query: Req['getIdentityFeatureStates']) => ({
url: `environments/${
query.environment
}/${Utils.getIdentitiesEndpoint()}/${
query.user
}/${Utils.getFeatureStatesEndpoint()}/all/`,
}),
}),
// END OF ENDPOINTS
}),
})

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

export const {
useGetIdentityFeatureStatesQuery,
// 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
*/
76 changes: 76 additions & 0 deletions frontend/common/services/useProjectFlag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { PagedResponse, ProjectFlag, Res } from 'common/types/responses'
import { Req } from 'common/types/requests'
import { service } from 'common/service'
import data from 'common/data/base/_data'
import { BaseQueryFn } from '@reduxjs/toolkit/query'

function recursivePageGet(
url: string,
parentRes: null | PagedResponse<ProjectFlag>,
baseQuery: (arg: unknown) => any, // matches rtk types,
) {
return baseQuery({
method: 'GET',
url,
}).then((res: Res['projectFlags']) => {
let response
if (parentRes) {
response = {
...parentRes,
results: parentRes.results.concat(res.results),
}
} else {
response = res
}
if (res.next) {
return recursivePageGet(res.next, response, baseQuery)
}
return Promise.resolve(response)
})
}
export const projectFlagService = service
.enhanceEndpoints({ addTagTypes: ['ProjectFlag'] })
.injectEndpoints({
endpoints: (builder) => ({
getProjectFlags: builder.query<
Res['projectFlags'],
Req['getProjectFlags']
>({
providesTags: (res, _, req) => [
{ id: req?.project, type: 'ProjectFlag' },
],
queryFn: async (args, _, _2, baseQuery) => {
return await recursivePageGet(
`projects/${args.project}/features/?page_size=999`,
null,
baseQuery,
)
},
}),
// END OF ENDPOINTS
}),
})

export async function getProjectFlags(
store: any,
data: Req['getProjectFlags'],
options?: Parameters<
typeof projectFlagService.endpoints.getProjectFlags.initiate
>[1],
) {
return store.dispatch(
projectFlagService.endpoints.getProjectFlags.initiate(data, options),
)
}
// END OF FUNCTION_EXPORTS

export const {
useGetProjectFlagsQuery,
// END OF EXPORTS
} = projectFlagService

/* Usage examples:
const { data, isLoading } = useGetProjectFlagsQuery({ id: 2 }, {}) //get hook
const [createProjectFlags, { isLoading, data, isSuccess }] = useCreateProjectFlagsMutation() //create hook
projectFlagService.endpoints.getProjectFlags.select({id: 2})(store.getState()) //access data from any function
*/
10 changes: 5 additions & 5 deletions frontend/common/stores/feature-list-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,9 @@ const controller = {
return createSegmentOverride(
getStore(),
{
enabled: !!v.enabled,
environmentId,
featureId: featureFlagId,
enabled: !!v.enabled,
feature_segment: {
segment: v.segment,
},
Expand All @@ -235,10 +235,6 @@ const controller = {
const newValue = {
environment: segmentOverride.data.environment,
feature: featureFlagId,
id: segmentOverride.data.feature_segment.id,
priority: segmentOverride.data.feature_segment.priority,
segment: segmentOverride.data.feature_segment.segment,
uuid: segmentOverride.data.feature_segment.uuid,
feature_segment_value: {
change_request: segmentOverride.data.change_request,
created_at: segmentOverride.data.created_at,
Expand All @@ -253,8 +249,12 @@ const controller = {
updated_at: segmentOverride.data.updated_at,
uuid: segmentOverride.data.uuid,
},
id: segmentOverride.data.feature_segment.id,
multivariate_options: segmentOverrides[i].multivariate_options,
priority: segmentOverride.data.feature_segment.priority,
segment: segmentOverride.data.feature_segment.segment,
segment_name: v.segment_name,
uuid: segmentOverride.data.feature_segment.uuid,
value: segmentOverrides[i].value,
}
segmentOverrides[i] = newValue
Expand Down
6 changes: 6 additions & 0 deletions frontend/common/types/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Account, Segment, Tag, FeatureStateValue } from './responses'
export type PagedRequest<T> = T & {
page?: number
page_size?: number
q?: string
}
export type OAuthType = 'github' | 'saml' | 'google'
export type PermissionLevel = 'organisation' | 'project' | 'environment'
Expand Down Expand Up @@ -99,5 +100,10 @@ export type Req = {
feature_segment: featureSegment
feature_state_value: FeatureStateValue
}
getIdentityFeatureStates: {
environment: string
user: string
}
getProjectFlags: { project: string }
// END OF TYPES
}
24 changes: 22 additions & 2 deletions frontend/common/types/responses.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// eslint-disable-next-line @typescript-eslint/no-empty-interface
import { type } from 'common/data/base/_data'
import UserGroupList from 'components/UserGroupList'

export type EdgePagedResponse<T> = PagedResponse<T> & {
last_evaluated_key?: string
Expand Down Expand Up @@ -188,6 +186,25 @@ export type MultivariateOption = {
default_percentage_allocation: number
}

export type FeatureType = 'STANDARD' | 'MULTIVARIATE'

export type IdentityFeatureState = {
feature: {
id: number
name: string
type: FeatureType
}
enabled: boolean
feature_state_value: FlagsmithValue
segment: null
multivariate_feature_state_values?: {
multivariate_feature_option: {
value: number
}
percentage_allocation: number
}[]
}

export type FeatureState = {
id: number
feature_state_value: string
Expand Down Expand Up @@ -316,5 +333,8 @@ export type Res = {
}
value: string
}

projectFlags: PagedResponse<ProjectFlag>
identityFeatureStates: IdentityFeatureState[]
// END OF TYPES
}
2 changes: 1 addition & 1 deletion frontend/web/components/AlertBar.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import ModalClose from './modals/base/ModalClose';
import ModalClose from './modals/base/ModalClose'

const AlertBar = class extends React.Component {
state = {}
Expand Down
20 changes: 16 additions & 4 deletions frontend/web/components/CompareEnvironments.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import FeatureListStore from 'common/stores/feature-list-store'
import ConfigProvider from 'common/providers/ConfigProvider'
import Permission from 'common/providers/Permission'
import Tag from './tags/Tag'
import { getProjectFlags } from 'common/services/useProjectFlag'
import { getStore } from 'common/store'

const featureNameWidth = 300

Expand Down Expand Up @@ -45,8 +47,8 @@ class CompareEnvironments extends Component {
return Promise.all([
this.state.projectFlags
? Promise.resolve({ results: this.state.projectFlags })
: data.get(
`${Project.api}projects/${this.props.projectId}/features/?page_size=999`,
: getProjectFlags(getStore(), { project: this.props.projectId }).then(
(res) => res.data,
),
data.get(
`${Project.api}environments/${this.state.environmentLeft}/featurestates/?page_size=999`,
Expand Down Expand Up @@ -122,7 +124,12 @@ class CompareEnvironments extends Component {
<Row>
<div style={{ width: featureNameWidth }}>
<EnvironmentSelect
ignoreAPIKey={this.state.environmentRight}
ignoreAPIKey={
this.state.environmentRight
? [this.state.environmentRight]
: undefined
}
projectId={this.props.projectId}
onChange={(environmentLeft) =>
this.setState({ environmentLeft })
}
Expand All @@ -136,7 +143,12 @@ class CompareEnvironments extends Component {

<div style={{ width: featureNameWidth }}>
<EnvironmentSelect
ignoreAPIKey={this.state.environmentLeft}
projectId={this.props.projectId}
ignore={
this.state.environmentLeft
? [this.state.environmentLeft]
: undefined
}
onChange={(environmentRight) =>
this.setState({ environmentRight })
}
Expand Down

3 comments on commit aafce13

@vercel
Copy link

@vercel vercel bot commented on aafce13 Aug 10, 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 aafce13 Aug 10, 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 aafce13 Aug 10, 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.