Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Compare identities #2616

Merged
merged 7 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading