Skip to content

Commit

Permalink
feat: Import export environment flags (#3161)
Browse files Browse the repository at this point in the history
Co-authored-by: Matthew Elwell <matthew.elwell@flagsmith.com>
  • Loading branch information
kyle-ssg and matthewelwell committed Feb 13, 2024
1 parent d516b4d commit 7b8c8dc
Show file tree
Hide file tree
Showing 44 changed files with 1,936 additions and 330 deletions.
1 change: 1 addition & 0 deletions frontend/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,4 +494,5 @@ export default {
'#AAC200',
'#DE3163',
],
untaggedTag: { color: '#dedede', label: 'Untagged' },
}
88 changes: 88 additions & 0 deletions frontend/common/services/useFeatureExport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Res } from 'common/types/responses'
import { Req } from 'common/types/requests'
import { service } from 'common/service'

export const featureExportService = service
.enhanceEndpoints({ addTagTypes: ['FeatureExport'] })
.injectEndpoints({
endpoints: (builder) => ({
createFeatureExport: builder.mutation<
Res['featureExport'],
Req['createFeatureExport']
>({
invalidatesTags: [{ id: 'LIST', type: 'FeatureExport' }],
query: (query: Req['createFeatureExport']) => ({
body: query,
method: 'POST',
url: `features/create-feature-export/`,
}),
}),
getFeatureExport: builder.query<
Res['featureExport'],
Req['getFeatureExport']
>({
providesTags: (res) => [{ id: res?.id, type: 'FeatureExport' }],
query: (query: Req['getFeatureExport']) => ({
url: `features/download-feature-export/${query.id}/`,
}),
}),
getFeatureExports: builder.query<
Res['featureExports'],
Req['getFeatureExports']
>({
providesTags: [{ id: 'LIST', type: 'FeatureExport' }],
query: (query) => ({
url: `projects/${query.projectId}/feature-exports/`,
}),
}),
// END OF ENDPOINTS
}),
})

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

export const {
useCreateFeatureExportMutation,
useGetFeatureExportQuery,
useGetFeatureExportsQuery,
// END OF EXPORTS
} = featureExportService

/* Usage examples:
const { data, isLoading } = useGetFeatureExportQuery({ id: 2 }, {}) //get hook
const [createFeatureExport, { isLoading, data, isSuccess }] = useCreateFeatureExportMutation() //create hook
featureExportService.endpoints.getFeatureExport.select({id: 2})(store.getState()) //access data from any function
*/
44 changes: 44 additions & 0 deletions frontend/common/services/useFeatureImport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Res } from 'common/types/responses'
import { Req } from 'common/types/requests'
import { service } from 'common/service'

export const featureImportService = service
.enhanceEndpoints({ addTagTypes: ['FeatureImport'] })
.injectEndpoints({
endpoints: (builder) => ({
getFeatureImports: builder.query<
Res['featureImports'],
Req['getFeatureImports']
>({
providesTags: [{ id: 'LIST', type: 'FeatureImport' }],
query: (query) => ({
url: `projects/${query.projectId}/feature-imports/`,
}),
}),
// END OF ENDPOINTS
}),
})

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

export const {
useGetFeatureImportsQuery,
// END OF EXPORTS
} = featureImportService

/* Usage examples:
const { data, isLoading } = useGetFeatureImportsQuery({ id: 2 }, {}) //get hook
const [createFeatureImports, { isLoading, data, isSuccess }] = useCreateFeatureImportsMutation() //create hook
featureImportService.endpoints.getFeatureImports.select({id: 2})(store.getState()) //access data from any function
*/
59 changes: 59 additions & 0 deletions frontend/common/services/useFlagsmithProjectImport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Res } from 'common/types/responses'
import { Req } from 'common/types/requests'
import { service } from 'common/service'
import toFormData from 'common/utils/toFormData'

export const flagsmithProjectImportService = service
.enhanceEndpoints({ addTagTypes: ['FlagsmithProjectImport'] })
.injectEndpoints({
endpoints: (builder) => ({
createFlagsmithProjectImport: builder.mutation<
Res['flagsmithProjectImport'],
Req['createFlagsmithProjectImport']
>({
invalidatesTags: [{ id: 'LIST', type: 'FlagsmithProjectImport' }],
queryFn: async (query, baseQueryApi, extraOptions, baseQuery) => {
const { environment_id, ...rest } = query
const formData = toFormData({ ...rest })

const { data, error } = await baseQuery({
body: formData,
method: 'POST',
url: `features/feature-import/${environment_id}`,
})
return { data, error }
},
}),
// END OF ENDPOINTS
}),
})

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

export const {
useCreateFlagsmithProjectImportMutation,
// END OF EXPORTS
} = flagsmithProjectImportService

/* Usage examples:
const { data, isLoading } = useGetFlagsmithProjectImportQuery({ id: 2 }, {}) //get hook
const [createFlagsmithProjectImport, { isLoading, data, isSuccess }] = useCreateFlagsmithProjectImportMutation() //create hook
flagsmithProjectImportService.endpoints.getFlagsmithProjectImport.select({id: 2})(store.getState()) //access data from any function
*/
5 changes: 4 additions & 1 deletion frontend/common/services/useGroupWithRole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ export const groupWithRoleService = service
Res['groupWithRole'],
Req['deleteGroupWithRole']
>({
invalidatesTags: [{ type: 'GroupWithRole' }, { type: 'RolePermissionGroup' }],
invalidatesTags: [
{ type: 'GroupWithRole' },
{ type: 'RolePermissionGroup' },
],
query: (query: Req['deleteGroupWithRole']) => ({
body: query,
method: 'DELETE',
Expand Down
5 changes: 1 addition & 4 deletions frontend/common/services/useRolesUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ export const rolesUserService = service
Res['rolesUsers'],
Req['deleteRolesPermissionUsers']
>({
invalidatesTags: [
{ type: 'User-role' },
{ type: 'RolesUser' },
],
invalidatesTags: [{ type: 'User-role' }, { type: 'RolesUser' }],
query: (query: Req['deleteRolesPermissionUsers']) => ({
body: query,
method: 'DELETE',
Expand Down
2 changes: 2 additions & 0 deletions frontend/common/stores/project-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ const controller = {
const store = Object.assign({}, BaseStore, {
getEnvironment: (api_key) =>
store.model && _.find(store.model.environments, { api_key }),
getEnvironmentById: (id) =>
store.model && _.find(store.model.environments, { id }),
getEnvironmentIdFromKey: (api_key) => {
const env = _.find(store.model.environments, { api_key })
return env && env.id
Expand Down
27 changes: 26 additions & 1 deletion frontend/common/types/requests.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Account, Segment, Tag, FeatureStateValue, Role } from './responses'
import {
Account,
Segment,
Tag,
FeatureStateValue,
Role,
ImportStrategy,
} from './responses'

export type PagedRequest<T> = T & {
page?: number
Expand Down Expand Up @@ -139,6 +146,24 @@ export type Req = {
token: string
}
}
createFeatureExport: {
environment_id: string
tag_ids?: (number | string)[]
}
getFeatureExport: {
id: string
}
getFeatureExports: {
projectId: string
}
createFlagsmithProjectImport: {
environment_id: number | string
strategy: ImportStrategy
file: File
}
getFeatureImports: {
projectId: string
}
getLaunchDarklyProjectImport: { project_id: string; import_id: string }
getLaunchDarklyProjectsImport: { project_id: string }
getUserWithRoles: { org_id: string; user_id: string }
Expand Down
44 changes: 39 additions & 5 deletions frontend/common/types/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export type Environment = {
name: string
api_key: string
description?: string
banner_text?: string
banner_colour?: string
project: number
minimum_change_request_approvals?: number
allow_client_traits: boolean
Expand All @@ -83,7 +85,34 @@ export type Project = {
total_segments?: number
environments: Environment[]
}
export type ImportStrategy = 'SKIP' | 'OVERWRITE_DESTRUCTIVE'

export type ImportExportStatus = 'SUCCESS' | 'PROCESSING' | 'FAILED'

export type FeatureImport = {
id: number
status: ImportExportStatus
strategy: string
environment_id: number
created_at: string
}

export type FeatureExport = {
id: string
name: string
environment_id: string
status: ImportExportStatus
created_at: string
}
export type FeatureImportItem = {
name: string
default_enabled: boolean
is_server_key_only: boolean
initial_value: FlagsmithValue
value: FlagsmithValue
enabled: false
multivariate: []
}
export type LaunchDarklyProjectImport = {
id: number
created_by: string
Expand Down Expand Up @@ -228,6 +257,7 @@ export type MultivariateOption = {
}

export type FeatureType = 'STANDARD' | 'MULTIVARIATE'
export type TagStrategy = 'INTERSECTION' | 'UNION'

export type IdentityFeatureState = {
feature: {
Expand All @@ -248,7 +278,7 @@ export type IdentityFeatureState = {

export type FeatureState = {
id: number
feature_state_value: string
feature_state_value: FlagsmithValue
multivariate_feature_state_values: MultivariateFeatureStateValue[]
identity?: string
uuid: string
Expand All @@ -265,11 +295,11 @@ export type FeatureState = {
}

export type ProjectFlag = {
created_date: Date
created_date: string
default_enabled: boolean
description?: string
id: number
initial_value: string
initial_value: FlagsmithValue
is_archived: boolean
is_server_key_only: boolean
multivariate_options: MultivariateOption[]
Expand Down Expand Up @@ -400,10 +430,14 @@ export type Res = {
environment: Environment
launchDarklyProjectImport: LaunchDarklyProjectImport
launchDarklyProjectsImport: LaunchDarklyProjectImport[]
userWithRoles: PagedResponse<Roles>
groupWithRole: PagedResponse<Roles>
userWithRoles: PagedResponse<Role>
groupWithRole: PagedResponse<Role>
changeRequests: PagedResponse<ChangeRequestSummary>
groupSummaries: UserGroupSummary[]
auditLogItem: AuditLogDetail
featureExport: { id: string }
featureExports: PagedResponse<FeatureExport>
flagsmithProjectImport: { id: string }
featureImports: PagedResponse<FeatureImport>
// END OF TYPES
}

0 comments on commit 7b8c8dc

Please sign in to comment.