Skip to content

Commit 5d2baa3

Browse files
Zaimwa9woodpre-commit-ci[bot]talissoncosta
authored
feat: create custom fields for projects (#6737)
Co-authored-by: wood <wadii.zaim@kosmo.delivery> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Talisson <talisson.odcosta@gmail.com>
1 parent 7617737 commit 5d2baa3

15 files changed

Lines changed: 429 additions & 368 deletions

frontend/common/services/useMetadataField.ts

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1+
import { sortBy } from 'lodash'
2+
13
import { Res } from 'common/types/responses'
24
import { Req } from 'common/types/requests'
35
import { service } from 'common/service'
6+
import transformCorePaging from 'common/transformCorePaging'
47
import Utils from 'common/utils/utils'
58
import { CustomMetadataField } from 'common/types/metadata-field'
69
import {
710
Environment,
11+
Metadata,
812
MetadataField,
9-
MetadataModelField,
1013
PagedResponse,
1114
ProjectFlag,
1215
Segment,
1316
} from 'common/types/responses'
14-
import { mergeMetadataFields } from 'common/utils/mergeMetadataFields'
1517

1618
type EntityType = 'feature' | 'segment' | 'environment'
1719

@@ -25,13 +27,13 @@ type EntityMetadataParams = {
2527

2628
type EntityData = ProjectFlag | Segment | Environment
2729

30+
type EntityWithMetadata = {
31+
metadata?: Metadata[]
32+
}
33+
2834
function getEntityUrl(params: EntityMetadataParams): string | null {
2935
const { entityId, entityType, projectId } = params
3036

31-
if (!entityId) {
32-
return null
33-
}
34-
3537
switch (entityType) {
3638
case 'feature':
3739
return `projects/${projectId}/features/${entityId}/`
@@ -87,13 +89,12 @@ export const metadataService = service
8789
// Build queries to run in parallel
8890
const queries: Promise<{ data?: unknown; error?: unknown }>[] = [
8991
baseQuery({
90-
url: `metadata/fields/?${Utils.toParam({
91-
organisation: arg.organisationId,
92+
url: `projects/${arg.projectId}/metadata/fields/?${Utils.toParam({
93+
entity: arg.entityType,
94+
include_organisation: true,
95+
page_size: 100,
9296
})}`,
9397
}),
94-
baseQuery({
95-
url: `organisations/${arg.organisationId}/metadata-model-fields/`,
96-
}),
9798
]
9899

99100
// Only fetch entity data if we have an entityId
@@ -104,30 +105,54 @@ export const metadataService = service
104105
// Fetch all in parallel
105106
const results = await Promise.all(queries)
106107

107-
const [fieldsRes, modelFieldsRes, entityRes] = results
108+
const [fieldsRes, entityRes] = results
108109

109110
// Handle errors
110111
if (fieldsRes.error) {
111112
return { error: fieldsRes.error as Res['metadataList'] }
112113
}
113-
if (modelFieldsRes.error) {
114-
return {
115-
error: modelFieldsRes.error as Res['metadataModelFieldList'],
116-
}
117-
}
114+
118115
if (entityRes?.error) {
119116
return { error: entityRes.error as EntityData }
120117
}
121118

122-
// Merge and return
123-
const mergedMetadata = mergeMetadataFields(
124-
fieldsRes.data as PagedResponse<MetadataField>,
125-
modelFieldsRes.data as PagedResponse<MetadataModelField>,
126-
entityRes?.data as EntityData | null,
127-
arg.entityContentType,
128-
)
119+
const fieldList = fieldsRes.data as PagedResponse<MetadataField>
120+
const entityData = (entityRes?.data ??
121+
null) as EntityWithMetadata | null
122+
123+
// Map fields to custom metadata fields with required status
124+
const fieldsForContentType: CustomMetadataField[] =
125+
fieldList.results.map((meta) => {
126+
const matchingModelField = meta.model_fields.find(
127+
(mf) => mf.content_type === arg.entityContentType,
128+
)
129+
return {
130+
...meta,
131+
isRequiredFor: !!matchingModelField?.is_required_for.length,
132+
metadataModelFieldId: matchingModelField
133+
? matchingModelField.id
134+
: null,
135+
}
136+
})
137+
138+
// Get existing values from the entity
139+
const existingValues: Metadata[] = entityData?.metadata ?? []
140+
141+
// Merge field definitions with existing values
142+
const mergedMetadata = fieldsForContentType.map((field) => {
143+
const existingValue = existingValues.find(
144+
(v) => v.model_field === field.metadataModelFieldId,
145+
)
146+
return {
147+
...field,
148+
field_value: existingValue?.field_value ?? '',
149+
hasValue: !!existingValue,
150+
}
151+
})
129152

130-
return { data: mergedMetadata }
153+
return {
154+
data: sortBy(mergedMetadata, (m) => (m.isRequiredFor ? -1 : 1)),
155+
}
131156
},
132157
}),
133158
getMetadataField: builder.query<
@@ -147,6 +172,25 @@ export const metadataService = service
147172
query: (query: Req['getMetadataList']) => ({
148173
url: `metadata/fields/?${Utils.toParam(query)}`,
149174
}),
175+
transformResponse: (res: Res['metadataList'], _, req) =>
176+
transformCorePaging(req, res),
177+
}),
178+
getProjectMetadataFieldList: builder.query<
179+
Res['projectMetadataFieldList'],
180+
Req['getProjectMetadataFieldList']
181+
>({
182+
providesTags: [{ id: 'LIST', type: 'Metadata' }],
183+
query: (query: Req['getProjectMetadataFieldList']) => ({
184+
url: `projects/${query.project_id}/metadata/fields/?${Utils.toParam({
185+
...(query.include_organisation
186+
? { include_organisation: true }
187+
: {}),
188+
page: query.page,
189+
page_size: query.page_size,
190+
})}`,
191+
}),
192+
transformResponse: (res: Res['projectMetadataFieldList'], _, req) =>
193+
transformCorePaging(req, res),
150194
}),
151195
updateMetadataField: builder.mutation<
152196
Res['metadataField'],
@@ -229,6 +273,20 @@ export async function getEntityMetadataFields(
229273
metadataService.endpoints.getEntityMetadataFields.initiate(data, options),
230274
)
231275
}
276+
export async function getProjectMetadataFieldList(
277+
store: any,
278+
data: Req['getProjectMetadataFieldList'],
279+
options?: Parameters<
280+
typeof metadataService.endpoints.getProjectMetadataFieldList.initiate
281+
>[1],
282+
) {
283+
return store.dispatch(
284+
metadataService.endpoints.getProjectMetadataFieldList.initiate(
285+
data,
286+
options,
287+
),
288+
)
289+
}
232290
// END OF FUNCTION_EXPORTS
233291

234292
export const {
@@ -237,6 +295,7 @@ export const {
237295
useGetEntityMetadataFieldsQuery,
238296
useGetMetadataFieldListQuery,
239297
useGetMetadataFieldQuery,
298+
useGetProjectMetadataFieldListQuery,
240299
useUpdateMetadataFieldMutation,
241300
// END OF EXPORTS
242301
} = metadataService

frontend/common/stores/project-store.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ const controller = {
2727
? data.post(`${Project.api}environments/${cloneId}/clone/`, {
2828
clone_feature_states_async: cloneFeatureStatesAsync,
2929
description,
30+
metadata: metadata || [],
3031
name,
3132
})
3233
: data.post(`${Project.api}environments/`, {
3334
description,
35+
metadata: metadata || [],
3436
name,
3537
project: projectId,
3638
})

frontend/common/types/requests.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,14 +425,19 @@ export type Req = {
425425
}
426426
}
427427
getMetadataField: { organisation_id: number }
428-
getMetadataList: { organisation: number }
428+
getMetadataList: PagedRequest<{ organisation: number }>
429+
getProjectMetadataFieldList: PagedRequest<{
430+
project_id: number
431+
include_organisation?: boolean
432+
}>
429433
updateMetadataField: {
430434
id: number
431435
body: {
432436
name: string
433437
type: string
434438
description: string
435439
organisation: number
440+
project?: number | null
436441
}
437442
}
438443
deleteMetadataField: { id: number }
@@ -442,6 +447,7 @@ export type Req = {
442447
name: string
443448
organisation: number
444449
type: string
450+
project?: number | null
445451
}
446452
}
447453

frontend/common/types/responses.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,12 +772,20 @@ export type Metadata = {
772772
field_value: string
773773
}
774774

775+
export type MetadataFieldModelField = {
776+
id: number
777+
content_type: number
778+
is_required_for: isRequiredFor[]
779+
}
780+
775781
export type MetadataField = {
776782
id: number
777783
name: string
778784
type: string
779785
description: string
780786
organisation: number
787+
project: number | null
788+
model_fields: MetadataFieldModelField[]
781789
}
782790

783791
export type ContentType = {
@@ -789,6 +797,7 @@ export type ContentType = {
789797

790798
export type isRequiredFor = {
791799
content_type: number
800+
object_id: number
792801
}
793802

794803
export type MetadataModelField = {
@@ -1120,6 +1129,7 @@ export type Res = {
11201129
metadataModelFieldList: PagedResponse<MetadataModelField>
11211130
metadataModelField: MetadataModelField
11221131
metadataList: PagedResponse<MetadataField>
1132+
projectMetadataFieldList: PagedResponse<MetadataField>
11231133
metadataField: MetadataField
11241134
launchDarklyProjectImport: LaunchDarklyProjectImport
11251135
launchDarklyProjectsImport: LaunchDarklyProjectImport[]

frontend/common/utils/__tests__/mergeMetadataFields.test.ts

Lines changed: 0 additions & 103 deletions
This file was deleted.

0 commit comments

Comments
 (0)