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: versioned segment override change request #3790

Merged
merged 12 commits into from
May 9, 2024
2 changes: 2 additions & 0 deletions frontend/common/dispatcher/app-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ const AppActions = Object.assign({}, require('./base/_app-actions'), {
segmentOverrides,
changeRequest,
commit,
mode,
) {
Dispatcher.handleViewAction({
actionType: Actions.EDIT_ENVIRONMENT_FLAG_CHANGE_REQUEST,
Expand All @@ -154,6 +155,7 @@ const AppActions = Object.assign({}, require('./base/_app-actions'), {
environmentFlag,
environmentId,
flag,
mode,
projectFlag,
projectId,
segmentOverrides,
Expand Down
8 changes: 5 additions & 3 deletions frontend/common/providers/FeatureListProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ const FeatureListProvider = class extends React.Component {
})
})

this.listenTo(FeatureListStore, 'saved', (isCreate) => {
this.props.onSave && this.props.onSave(isCreate)
this.listenTo(FeatureListStore, 'saved', (data) => {
this.props.onSave && this.props.onSave(data)
})

this.listenTo(FeatureListStore, 'problem', () => {
Expand Down Expand Up @@ -174,7 +174,7 @@ const FeatureListProvider = class extends React.Component {
}),
() => {
FeatureListStore.isSaving = false
FeatureListStore.trigger('saved')
FeatureListStore.trigger('saved', {})
FeatureListStore.trigger('change')
},
)
Expand All @@ -189,6 +189,7 @@ const FeatureListProvider = class extends React.Component {
segmentOverrides,
changeRequest,
commit,
mode,
) => {
AppActions.editFeatureMv(
projectId,
Expand Down Expand Up @@ -220,6 +221,7 @@ const FeatureListProvider = class extends React.Component {
segmentOverrides,
changeRequest,
commit,
mode,
)
},
)
Expand Down
21 changes: 21 additions & 0 deletions frontend/common/services/useFeatureSegment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ export const featureSegmentService = service
url: `features/feature-segments/${query.id}/`,
}),
}),
getFeatureSegment: builder.query<
Res['featureSegment'],
Req['getFeatureSegment']
>({
providesTags: (res) => [{ id: res?.id, type: 'FeatureSegment' }],
query: (query: Req['getFeatureSegment']) => ({
url: `features/feature-segments/${query.id}/`,
}),
}),
// END OF ENDPOINTS
}),
})
Expand All @@ -35,10 +44,22 @@ export async function deleteFeatureSegment(
),
)
}
export async function getFeatureSegment(
store: any,
data: Req['getFeatureSegment'],
options?: Parameters<
typeof featureSegmentService.endpoints.getFeatureSegment.initiate
>[1],
) {
return store.dispatch(
featureSegmentService.endpoints.getFeatureSegment.initiate(data, options),
)
}
// END OF FUNCTION_EXPORTS

export const {
useDeleteFeatureSegmentMutation,
useGetFeatureSegmentQuery,
// END OF EXPORTS
} = featureSegmentService

Expand Down
79 changes: 79 additions & 0 deletions frontend/common/services/useFeatureState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { FeatureState, PagedResponse, Res } from 'common/types/responses'
import { Req } from 'common/types/requests'
import { service } from 'common/service'
import Utils from 'common/utils/utils'
import { getFeatureSegment } from './useFeatureSegment'
import { getStore } from 'common/store'
const _data = require('../data/base/_data')
export const addFeatureSegmentsToFeatureStates = async (v) => {
if (!v.feature_segment) {
return v
}
const featureSegmentData = await getFeatureSegment(getStore(), {
id: v.feature_segment,
})
return {
...v,
feature_segment: featureSegmentData.data,
}
}
export const featureStateService = service
.enhanceEndpoints({ addTagTypes: ['FeatureState'] })
.injectEndpoints({
endpoints: (builder) => ({
getFeatureStates: builder.query<
Res['featureStates'],
Req['getFeatureStates']
>({
providesTags: [{ id: 'LIST', type: 'FeatureState' }],
queryFn: async (query, baseQueryApi, extraOptions, baseQuery) => {
//This endpoint returns feature_segments as a number, so it fetches the feature segments and appends
const {
data,
}: {
data: PagedResponse<
Omit<FeatureState, 'feature_segment'> & {
feature_segment: number | null
}
>
} = await baseQuery({
url: `features/featurestates/?${Utils.toParam(query)}`,
})
const results = await Promise.all(
data.results.map(addFeatureSegmentsToFeatureStates),
)
return {
data: {
...data,
results,
},
}
},
}),
// END OF ENDPOINTS
}),
})

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

export const {
useGetFeatureStatesQuery,
// END OF EXPORTS
} = featureStateService

/* Usage examples:
const { data, isLoading } = useGetFeatureStatesQuery({ id: 2 }, {}) //get hook
const [createFeatureStates, { isLoading, data, isSuccess }] = useCreateFeatureStatesMutation() //create hook
featureStateService.endpoints.getFeatureStates.select({id: 2})(store.getState()) //access data from any function
*/
29 changes: 26 additions & 3 deletions frontend/common/services/useFeatureVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { deleteFeatureSegment } from './useFeatureSegment'
import transformCorePaging from 'common/transformCorePaging'
import Utils from 'common/utils/utils'
import { updateSegmentPriorities } from './useSegmentPriority'

export const featureVersionService = service
.enhanceEndpoints({ addTagTypes: ['FeatureVersion'] })
Expand All @@ -27,16 +28,18 @@ export const featureVersionService = service
environmentId: query.environmentId,
featureId: query.featureId,
})

// Step 2: Get the feature states for the live version
const currentFeatureStates: { data: FeatureState[] } =
await getVersionFeatureState(getStore(), {
environmentId: query.environmentId,
featureId: query.featureId,
sha: versionRes.data.uuid,
})
const res = await Promise.all(

// Step 3: update, create or delete feature states from the new version
const res: { data: FeatureState }[] = await Promise.all(
query.featureStates.map((featureState) => {
// Step 3: update, create or delete feature states from the new version
const matchingVersionState = currentFeatureStates.data.find(
(feature) => {
return (
Expand Down Expand Up @@ -110,14 +113,34 @@ export const featureVersionService = service
}
}),
)

//Step 4: Update feature segment priorities before saving feature states
const prioritiesToUpdate = query.featureStates
.filter((v) => !v.toRemove && !!v.feature_segment)
.map((v) => {
const matchingFeatureSegment = res?.find(
(currentFeatureState) =>
v.feature_segment?.segment ===
currentFeatureState.data.feature_segment?.segment,
)
return {
id: matchingFeatureSegment!.data.feature_segment!.id!,
priority: v.feature_segment!.priority,
}
})
if (prioritiesToUpdate.length) {
await updateSegmentPriorities(getStore(), prioritiesToUpdate)
}

const ret = {
data: res.map((item) => ({
...item,
version_sha: versionRes.data.uuid,
})),
error: res.find((v) => !!v.error)?.error,
}
// Step 4: Publish the feature version

// Step 5: Publish the feature version
if (!query.skipPublish) {
await publishFeatureVersion(getStore(), {
environmentId: query.environmentId,
Expand Down
66 changes: 47 additions & 19 deletions frontend/common/stores/change-requests-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,31 @@ const Dispatcher = require('../dispatcher/dispatcher')
const BaseStore = require('./base/_store')
const data = require('../data/base/_data')
const { flatten } = require('lodash')
const {
addFeatureSegmentsToFeatureStates,
} = require('../services/useFeatureState')

const PAGE_SIZE = 20
const transformChangeRequest = (changeRequest) => {
if (changeRequest?.environment_feature_versions?.length) {
return {
...changeRequest,
feature_states: flatten(
changeRequest.environment_feature_versions.map(
(featureVersion) => featureVersion.feature_states,
const transformChangeRequest = async (changeRequest) => {
const res = changeRequest?.environment_feature_versions?.length
? {
...changeRequest,
feature_states: flatten(
changeRequest.environment_feature_versions.map(
(featureVersion) => featureVersion.feature_states,
),
),
),
}
}
: changeRequest

const feature_states = await Promise.all(
res.feature_states.map(addFeatureSegmentsToFeatureStates),
)

return {
...res,
feature_states,
}
return changeRequest
}
const controller = {
actionChangeRequest: (id, action, cb) => {
Expand All @@ -25,8 +36,8 @@ const controller = {
.then(() => {
data
.get(`${Project.api}features/workflows/change-requests/${id}/`)
.then((res) => {
store.model[id] = transformChangeRequest(res)
.then(async (res) => {
store.model[id] = await transformChangeRequest(res)
cb && cb()
store.loaded()
})
Expand All @@ -47,8 +58,8 @@ const controller = {
store.loading()
data
.get(`${Project.api}features/workflows/change-requests/${id}/`)
.then((apiResponse) => {
const res = transformChangeRequest(apiResponse)
.then(async (apiResponse) => {
const res = await transformChangeRequest(apiResponse)
return Promise.all([
data.get(
`${Project.api}environments/${environmentId}/featurestates/?feature=${res.feature_states[0].feature}`,
Expand Down Expand Up @@ -114,15 +125,32 @@ const controller = {
updateChangeRequest: (changeRequest) => {
store.loading()
data
.put(
.get(
`${Project.api}features/workflows/change-requests/${changeRequest.id}/`,
changeRequest,
)
.then((res) => {
store.model[changeRequest.id] = transformChangeRequest(res)
store.loaded()
data
.put(
`${Project.api}features/workflows/change-requests/${changeRequest.id}/`,
{
...res,
approvals: changeRequest.approvals,
description: changeRequest.description,
environment_feature_versions:
changeRequest?.environment_feature_versions?.map((v) => v.uuid),
group_assignments: changeRequest.group_assignments,
title: changeRequest.title,
},
)
.then(async () => {
const res = await data.get(
`${Project.api}features/workflows/change-requests/${changeRequest.id}/`,
)
store.model[changeRequest.id] = await transformChangeRequest(res)
store.loaded()
})
.catch((e) => API.ajaxHandler(store, e))
})
.catch((e) => API.ajaxHandler(store, e))
},
}

Expand Down
Loading