Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
5ac05e2
feat: calculate-has-metadata-required-based-on-all-entities
Zaimwa9 Feb 4, 2026
e895451
fix: calculate-has-metadata-required-based-on-all-entities
Zaimwa9 Feb 4, 2026
b81f1d6
fix: calculate-has-metadata-required-based-on-all-entities
Zaimwa9 Feb 4, 2026
1305509
feat: refactor-metadata-with-hooks
Zaimwa9 Feb 5, 2026
9f65d7c
feat: consolidated-requests-and-merged-strategy-in-rtk
Zaimwa9 Feb 5, 2026
242cc97
Merge branch 'main' of github.com:Flagsmith/flagsmith into fix/requir…
Zaimwa9 Feb 16, 2026
6f8c33f
feat: resolved-review-comments
Zaimwa9 Feb 16, 2026
79771ed
feat: added-project-support-for-custom-fields
Zaimwa9 Feb 16, 2026
2c39ce8
feat: reworked-comments
Zaimwa9 Feb 17, 2026
1770e80
feat: add-project-support-to-metadata-types-and-service
Zaimwa9 Feb 17, 2026
9aeeaa5
feat: add-project-metadata-to-page-and-modal
Zaimwa9 Feb 17, 2026
7057609
feat: project-custom-fields-override-org-ones
Zaimwa9 Feb 17, 2026
9513dc8
feat: wrap-project-custom-fields-with-feature-flag
Zaimwa9 Feb 17, 2026
2c6cf8b
fix: skip-feature-metadata-fetch-when-creating-feature
Zaimwa9 Feb 17, 2026
e2dc65a
Merge branch 'fix/required-metadata-validation' of github.com:Flagsmi…
Zaimwa9 Feb 17, 2026
94dda99
Merge branch 'feat/support-project-level-custom-fields' of github.com…
Zaimwa9 Feb 17, 2026
37a269a
feat: inherited-badge-alignment
Zaimwa9 Feb 17, 2026
999c408
feat: invalidate-tags-on-delete-metadata
Zaimwa9 Feb 17, 2026
463d388
fix: fixed-parsing-error
Zaimwa9 Feb 17, 2026
0503ad3
feat: invalidate-tags-on-delete-metadata
Zaimwa9 Feb 17, 2026
aad191d
Merge branch 'fix/required-metadata-validation' of github.com:Flagsmi…
Zaimwa9 Feb 17, 2026
254eb91
Merge branch 'feat/support-project-level-custom-fields' of github.com…
Zaimwa9 Feb 17, 2026
cd7b03c
feat: reviewed-query-params-and-filtering
Zaimwa9 Feb 17, 2026
91f4218
Merge branch 'feat/support-project-level-custom-fields' of github.com…
Zaimwa9 Feb 17, 2026
d53df7e
feat: correctly-retrieve-project-versus-org-custom-fields
Zaimwa9 Feb 17, 2026
4fea476
feat: fetch-metadata-model-in-api-and-separate-custom-fields-project-…
Zaimwa9 Feb 18, 2026
b0249ff
Merge branch 'feat/support-project-level-custom-fields' of github.com…
Zaimwa9 Feb 18, 2026
707e67c
feat: separated-queries-for-org-and-project-custom-fields
Zaimwa9 Feb 18, 2026
4345db2
feat: added-paginations-to-custom-fields-table
Zaimwa9 Feb 18, 2026
0845386
feat: await-completion-of-model-field-requirements-to-update-ui
Feb 18, 2026
da57567
feat: fixed-segment-id-converted-to-undefined
Zaimwa9 Feb 19, 2026
790ff64
feat: added-pagination-and-filtering-by-entity-to-custom-fields
Zaimwa9 Feb 19, 2026
77b5b20
Merge branch 'feat/support-project-level-custom-fields' of github.com…
Zaimwa9 Feb 19, 2026
9ac59f7
feat: filter-custom-fields-get-request-by-entity
Zaimwa9 Feb 19, 2026
81f4323
Merge branch 'main' of github.com:Flagsmith/flagsmith into fix/requir…
Zaimwa9 Feb 19, 2026
3cc20aa
feat: fixed-use-effect-on-entity-id-switch
Zaimwa9 Feb 19, 2026
6d3bb18
Merge branch 'fix/required-metadata-validation' of github.com:Flagsmi…
Zaimwa9 Feb 19, 2026
ce1522a
feat: fixed-edit-delete-project-permissions-for-custom-fields
Zaimwa9 Feb 19, 2026
733ece1
feat: refactored-validated-required-metadata
Zaimwa9 Feb 19, 2026
0161047
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 19, 2026
986df6a
feat: removed-unused-variable
Zaimwa9 Feb 19, 2026
936dee9
Merge branch 'feat/support-project-level-custom-fields' of github.com…
Zaimwa9 Feb 19, 2026
67ac700
Merge branch 'feat/support-project-level-custom-fields' of github.com…
Zaimwa9 Feb 19, 2026
40c5620
feat: added-pagination-to-org-metadata-fields
Zaimwa9 Feb 19, 2026
7038fa2
Merge branch 'feat/support-project-level-custom-fields' of github.com…
Zaimwa9 Feb 19, 2026
bbc19c9
feat: wrap-mutation-to-better-handle-errors
Zaimwa9 Feb 19, 2026
3ed5419
feat: review-feedback-readibility
Zaimwa9 Feb 24, 2026
9698a0f
feat: rebased-main
Zaimwa9 Mar 2, 2026
f9da854
feat: removed-feature-flag
Zaimwa9 Mar 2, 2026
0c1556f
fix: standardise metadata ID types to number (#6817)
talissoncosta Mar 2, 2026
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
109 changes: 84 additions & 25 deletions frontend/common/services/useMetadataField.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { sortBy } from 'lodash'

import { Res } from 'common/types/responses'
import { Req } from 'common/types/requests'
import { service } from 'common/service'
import transformCorePaging from 'common/transformCorePaging'
import Utils from 'common/utils/utils'
import { CustomMetadataField } from 'common/types/metadata-field'
import {
Environment,
Metadata,
MetadataField,
MetadataModelField,
PagedResponse,
ProjectFlag,
Segment,
} from 'common/types/responses'
import { mergeMetadataFields } from 'common/utils/mergeMetadataFields'

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

Expand All @@ -25,13 +27,13 @@ type EntityMetadataParams = {

type EntityData = ProjectFlag | Segment | Environment

type EntityWithMetadata = {
metadata?: Metadata[]
}

function getEntityUrl(params: EntityMetadataParams): string | null {
const { entityId, entityType, projectId } = params

if (!entityId) {
return null
}

switch (entityType) {
case 'feature':
return `projects/${projectId}/features/${entityId}/`
Expand Down Expand Up @@ -87,13 +89,12 @@ export const metadataService = service
// Build queries to run in parallel
const queries: Promise<{ data?: unknown; error?: unknown }>[] = [
baseQuery({
url: `metadata/fields/?${Utils.toParam({
organisation: arg.organisationId,
url: `projects/${arg.projectId}/metadata/fields/?${Utils.toParam({
entity: arg.entityType,
include_organisation: true,
page_size: 100,
})}`,
}),
baseQuery({
url: `organisations/${arg.organisationId}/metadata-model-fields/`,
}),
]

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

const [fieldsRes, modelFieldsRes, entityRes] = results
const [fieldsRes, entityRes] = results

// Handle errors
if (fieldsRes.error) {
return { error: fieldsRes.error as Res['metadataList'] }
}
if (modelFieldsRes.error) {
return {
error: modelFieldsRes.error as Res['metadataModelFieldList'],
}
}

if (entityRes?.error) {
return { error: entityRes.error as EntityData }
}

// Merge and return
const mergedMetadata = mergeMetadataFields(
fieldsRes.data as PagedResponse<MetadataField>,
modelFieldsRes.data as PagedResponse<MetadataModelField>,
entityRes?.data as EntityData | null,
arg.entityContentType,
)
const fieldList = fieldsRes.data as PagedResponse<MetadataField>
const entityData = (entityRes?.data ??
null) as EntityWithMetadata | null

// Map fields to custom metadata fields with required status
const fieldsForContentType: CustomMetadataField[] =
fieldList.results.map((meta) => {
const matchingModelField = meta.model_fields.find(
(mf) => mf.content_type === arg.entityContentType,
)
return {
...meta,
isRequiredFor: !!matchingModelField?.is_required_for.length,
metadataModelFieldId: matchingModelField
? matchingModelField.id
: null,
}
})

// Get existing values from the entity
const existingValues: Metadata[] = entityData?.metadata ?? []

// Merge field definitions with existing values
const mergedMetadata = fieldsForContentType.map((field) => {
const existingValue = existingValues.find(
(v) => v.model_field === field.metadataModelFieldId,
)
return {
...field,
field_value: existingValue?.field_value ?? '',
hasValue: !!existingValue,
}
})

return { data: mergedMetadata }
return {
data: sortBy(mergedMetadata, (m) => (m.isRequiredFor ? -1 : 1)),
}
},
}),
getMetadataField: builder.query<
Expand All @@ -147,6 +172,25 @@ export const metadataService = service
query: (query: Req['getMetadataList']) => ({
url: `metadata/fields/?${Utils.toParam(query)}`,
}),
transformResponse: (res: Res['metadataList'], _, req) =>
transformCorePaging(req, res),
}),
getProjectMetadataFieldList: builder.query<
Res['projectMetadataFieldList'],
Req['getProjectMetadataFieldList']
>({
providesTags: [{ id: 'LIST', type: 'Metadata' }],
query: (query: Req['getProjectMetadataFieldList']) => ({
url: `projects/${query.project_id}/metadata/fields/?${Utils.toParam({
...(query.include_organisation
? { include_organisation: true }
: {}),
page: query.page,
page_size: query.page_size,
})}`,
}),
transformResponse: (res: Res['projectMetadataFieldList'], _, req) =>
transformCorePaging(req, res),
}),
updateMetadataField: builder.mutation<
Res['metadataField'],
Expand Down Expand Up @@ -229,6 +273,20 @@ export async function getEntityMetadataFields(
metadataService.endpoints.getEntityMetadataFields.initiate(data, options),
)
}
export async function getProjectMetadataFieldList(
store: any,
data: Req['getProjectMetadataFieldList'],
options?: Parameters<
typeof metadataService.endpoints.getProjectMetadataFieldList.initiate
>[1],
) {
return store.dispatch(
metadataService.endpoints.getProjectMetadataFieldList.initiate(
data,
options,
),
)
}
// END OF FUNCTION_EXPORTS

export const {
Expand All @@ -237,6 +295,7 @@ export const {
useGetEntityMetadataFieldsQuery,
useGetMetadataFieldListQuery,
useGetMetadataFieldQuery,
useGetProjectMetadataFieldListQuery,
useUpdateMetadataFieldMutation,
// END OF EXPORTS
} = metadataService
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 @@ -27,10 +27,12 @@ const controller = {
? data.post(`${Project.api}environments/${cloneId}/clone/`, {
clone_feature_states_async: cloneFeatureStatesAsync,
description,
metadata: metadata || [],
name,
})
: data.post(`${Project.api}environments/`, {
description,
metadata: metadata || [],
name,
project: projectId,
})
Expand Down
8 changes: 7 additions & 1 deletion frontend/common/types/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,14 +425,19 @@ export type Req = {
}
}
getMetadataField: { organisation_id: number }
getMetadataList: { organisation: number }
getMetadataList: PagedRequest<{ organisation: number }>
getProjectMetadataFieldList: PagedRequest<{
project_id: number
include_organisation?: boolean
}>
updateMetadataField: {
id: number
body: {
name: string
type: string
description: string
organisation: number
project?: number | null
}
}
deleteMetadataField: { id: number }
Expand All @@ -442,6 +447,7 @@ export type Req = {
name: string
organisation: number
type: string
project?: number | null
}
}

Expand Down
10 changes: 10 additions & 0 deletions frontend/common/types/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -772,12 +772,20 @@ export type Metadata = {
field_value: string
}

export type MetadataFieldModelField = {
id: number
content_type: number
is_required_for: isRequiredFor[]
}

export type MetadataField = {
id: number
name: string
type: string
description: string
organisation: number
project: number | null
model_fields: MetadataFieldModelField[]
}

export type ContentType = {
Expand All @@ -789,6 +797,7 @@ export type ContentType = {

export type isRequiredFor = {
content_type: number
object_id: number
}

export type MetadataModelField = {
Expand Down Expand Up @@ -1120,6 +1129,7 @@ export type Res = {
metadataModelFieldList: PagedResponse<MetadataModelField>
metadataModelField: MetadataModelField
metadataList: PagedResponse<MetadataField>
projectMetadataFieldList: PagedResponse<MetadataField>
metadataField: MetadataField
launchDarklyProjectImport: LaunchDarklyProjectImport
launchDarklyProjectsImport: LaunchDarklyProjectImport[]
Expand Down
103 changes: 0 additions & 103 deletions frontend/common/utils/__tests__/mergeMetadataFields.test.ts

This file was deleted.

Loading
Loading