From d4006c031436778227f64fd16cbc36f897769def Mon Sep 17 00:00:00 2001
From: Novak Zaballa <41410593+novakzaballa@users.noreply.github.com>
Date: Tue, 30 Jul 2024 11:53:37 -0400
Subject: [PATCH] fix: Metadata UI improvements (#4327)
---
frontend/common/stores/feature-list-store.ts | 4 +
frontend/common/stores/project-store.js | 33 ++++++---
frontend/common/utils/utils.tsx | 16 ++--
.../web/components/base/forms/InputGroup.js | 8 +-
.../metadata/AddMetadataToEntity.tsx | 55 +++++++-------
frontend/web/components/modals/CreateFlag.js | 73 ++++++++++---------
.../web/components/modals/CreateSegment.tsx | 8 +-
.../components/pages/CreateEnvironmentPage.js | 8 +-
.../pages/EnvironmentSettingsPage.js | 9 ++-
.../components/pages/ProjectSettingsPage.js | 4 +-
.../web/components/pages/SegmentsPage.tsx | 2 +-
11 files changed, 127 insertions(+), 93 deletions(-)
diff --git a/frontend/common/stores/feature-list-store.ts b/frontend/common/stores/feature-list-store.ts
index 1227914103b7..d833f865aa9a 100644
--- a/frontend/common/stores/feature-list-store.ts
+++ b/frontend/common/stores/feature-list-store.ts
@@ -161,6 +161,10 @@ const controller = {
})
.then((res) => {
// onComplete calls back preserving the order of multivariate_options with their updated ids
+ if (res.error) {
+ store.saved({ error: res.error })
+ return
+ }
if (onComplete) {
onComplete(res)
}
diff --git a/frontend/common/stores/project-store.js b/frontend/common/stores/project-store.js
index 1747bc10de6c..67f75b79269c 100644
--- a/frontend/common/stores/project-store.js
+++ b/frontend/common/stores/project-store.js
@@ -84,15 +84,30 @@ const controller = {
editEnv: (env) => {
API.trackEvent(Constants.events.EDIT_ENVIRONMENT)
- data.put(`${Project.api}environments/${env.api_key}/`, env).then((res) => {
- const index = _.findIndex(store.model.environments, { id: env.id })
- store.model.environments[index] = res
- store.saved()
- getStore().dispatch(
- environmentService.util.invalidateTags(['Environment']),
- )
- AppActions.refreshOrganisation()
- })
+ data
+ .put(`${Project.api}environments/${env.api_key}/`, env)
+ .then((res) => {
+ const index = _.findIndex(store.model.environments, { id: env.id })
+ store.model.environments[index] = res
+ store.saved()
+ getStore().dispatch(
+ environmentService.util.invalidateTags(['Environment']),
+ )
+ AppActions.refreshOrganisation()
+ })
+ .catch((e) => {
+ e.json()
+ .then((result) => {
+ if (result?.metadata?.[0]) {
+ toast(result.metadata[0], 'danger')
+ } else {
+ toast('Error updating the environment', 'danger')
+ }
+ })
+ .catch((e) => {
+ API.ajaxHandler(store, e)
+ })
+ })
},
editProject: (project) => {
store.saving()
diff --git a/frontend/common/utils/utils.tsx b/frontend/common/utils/utils.tsx
index 0a5f66716bf1..d4e6e6e54676 100644
--- a/frontend/common/utils/utils.tsx
+++ b/frontend/common/utils/utils.tsx
@@ -383,6 +383,10 @@ const Utils = Object.assign({}, require('./base/_utils'), {
valid = isEnterprise
break
}
+ case 'METADATA': {
+ valid = isEnterprise
+ break
+ }
default:
valid = true
break
@@ -536,16 +540,8 @@ const Utils = Object.assign({}, require('./base/_utils'), {
return /^-?\d*\.?\d+$/.test(`${value}`)
},
isValidURL(value: any) {
- const pattern = new RegExp(
- '^(https?:\\/\\/)?' + // protocol
- '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
- '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
- '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
- '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
- '(\\#[-a-z\\d_]*)?$',
- 'i',
- )
- return !!pattern.test(value)
+ const regex = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i
+ return regex.test(value)
},
loadScriptPromise(url: string) {
return new Promise((resolve) => {
diff --git a/frontend/web/components/base/forms/InputGroup.js b/frontend/web/components/base/forms/InputGroup.js
index b62e029658f7..6384aac4c5e9 100644
--- a/frontend/web/components/base/forms/InputGroup.js
+++ b/frontend/web/components/base/forms/InputGroup.js
@@ -22,9 +22,9 @@ const InputGroup = class extends Component {
const { inputProps, size } = this.props
return (
{this.props.tooltip ? (
) : (
diff --git a/frontend/web/components/metadata/AddMetadataToEntity.tsx b/frontend/web/components/metadata/AddMetadataToEntity.tsx
index 675504382384..187f39702706 100644
--- a/frontend/web/components/metadata/AddMetadataToEntity.tsx
+++ b/frontend/web/components/metadata/AddMetadataToEntity.tsx
@@ -9,12 +9,11 @@ import {
useUpdateEnvironmentMutation,
} from 'common/services/useEnvironment'
import { MetadataField, Metadata } from 'common/types/responses'
-import Input from 'components/base/forms/Input'
import Utils from 'common/utils/utils'
import { useGetProjectFlagQuery } from 'common/services/useProjectFlag'
-import Tooltip from 'components/Tooltip'
import { sortBy } from 'lodash'
import Switch from 'components/Switch'
+import InputGroup from 'components/base/forms/InputGroup'
export type CustomMetadataField = MetadataField & {
metadataModelFieldId: number | string | null
@@ -247,8 +246,8 @@ const AddMetadataToEntity: FC
= ({
}}
renderNoResults={
- No custom fields configured for {entity} entity. Add custom fields in
- your{' '}
+ No custom fields configured for {entity} entity. Add custom fields
+ in your{' '}
= ({
{`${metadata?.name} ${
metadata?.isRequiredFor ? '*' : ''
}`}
- {metadata?.type !== 'bool' ? (
-
- {
- setMetadataValue(Utils.safeParseEventValue(e))
- setMetadataValueChanged(true)
- }}
- className='mr-2'
- style={{ width: '250px' }}
- placeholder='Field Value'
- isValid={Utils.validateMetadataType(
- metadata?.type,
- metadataValue,
- )}
- />
- }
- place='top'
- >
- {`This value has to be of type ${metadata?.type}`}
-
-
- ) : (
+ {metadata?.type === 'bool' ? (
= ({
}}
/>
+ ) : (
+
+ {
+ setMetadataValue(Utils.safeParseEventValue(e))
+ setMetadataValueChanged(true)
+ }}
+ type='text'
+ />
+
)}
)
diff --git a/frontend/web/components/modals/CreateFlag.js b/frontend/web/components/modals/CreateFlag.js
index 27711dd42d7b..9dd51992ddb5 100644
--- a/frontend/web/components/modals/CreateFlag.js
+++ b/frontend/web/components/modals/CreateFlag.js
@@ -40,7 +40,6 @@ import { getSupportedContentType } from 'common/services/useSupportedContentType
import { getGithubIntegration } from 'common/services/useGithubIntegration'
import { removeUserOverride } from 'components/RemoveUserOverride'
import ExternalResourcesLinkTab from 'components/ExternalResourcesLinkTab'
-import MetadataTitle from 'components/metadata/MetadataTitle'
import { saveFeatureWithValidation } from 'components/saveFeatureWithValidation'
const CreateFlag = class extends Component {
@@ -182,7 +181,10 @@ const CreateFlag = class extends Component {
) {
this.getFeatureUsage()
}
- if (Utils.getFlagsmithHasFeature('enable_metadata')) {
+ if (
+ Utils.getPlansPermission('METADATA') &&
+ Utils.getFlagsmithHasFeature('enable_metadata')
+ ) {
getSupportedContentType(getStore(), {
organisation_id: AccountStore.getOrganisation().id,
}).then((res) => {
@@ -550,7 +552,9 @@ const CreateFlag = class extends Component {
const hideIdentityOverridesTab = Utils.getShouldHideIdentityOverridesTab()
const noPermissions = this.props.noPermissions
let regexValid = true
- const metadataEnable = Utils.getFlagsmithHasFeature('enable_metadata')
+ const metadataEnable =
+ Utils.getPlansPermission('METADATA') &&
+ Utils.getFlagsmithHasFeature('enable_metadata')
try {
if (!isEdit && name && regex) {
regexValid = name.match(new RegExp(regex))
@@ -580,32 +584,24 @@ const CreateFlag = class extends Component {
)}
{metadataEnable && featureContentType?.id && (
<>
- {
- this.setState({ visible: v })
+
+ {
+ this.setState({
+ hasMetadataRequired: b,
+ })
+ }}
+ onChange={(m) => {
+ this.setState({
+ metadata: m,
+ })
}}
- hasRequiredMetadata={this.state.hasMetadataRequired}
/>
- {(this.state.hasMetadataRequired || this.state.visible) && (
- {
- this.setState({
- hasMetadataRequired: b,
- })
- }}
- onChange={(m) => {
- this.setState({
- metadata: m,
- })
- }}
- />
- )}
>
)}
{!identity && projectFlag && (
@@ -1971,12 +1967,23 @@ const FeatureProvider = (WrappedComponent) => {
this.listenTo(
FeatureListStore,
'saved',
- ({ changeRequest, createdFlag, isCreate } = {}) => {
- toast(
- `${createdFlag || isCreate ? 'Created' : 'Updated'} ${
- changeRequest ? 'Change Request' : 'Feature'
- }`,
- )
+ ({ changeRequest, createdFlag, error, isCreate } = {}) => {
+ if (error?.data?.metadata) {
+ error.data.metadata?.forEach((m) => {
+ if (Object.keys(m).length > 0) {
+ toast(m.non_field_errors[0], 'danger')
+ }
+ })
+ } else if (error?.data) {
+ toast('Error updating the Flag', 'danger')
+ return
+ } else {
+ toast(
+ `${createdFlag || isCreate ? 'Created' : 'Updated'} ${
+ changeRequest ? 'Change Request' : 'Feature'
+ }`,
+ )
+ }
const envFlags = FeatureListStore.getEnvironmentFlags()
if (createdFlag) {
diff --git a/frontend/web/components/modals/CreateSegment.tsx b/frontend/web/components/modals/CreateSegment.tsx
index 1af2ef0a032c..fc3fbf4b34f3 100644
--- a/frontend/web/components/modals/CreateSegment.tsx
+++ b/frontend/web/components/modals/CreateSegment.tsx
@@ -154,7 +154,9 @@ const CreateSegment: FC = ({
const [metadata, setMetadata] = useState(
segment.metadata,
)
- const metadataEnable = Utils.getFlagsmithHasFeature('enable_metadata')
+ const metadataEnable =
+ Utils.getPlansPermission('METADATA') &&
+ Utils.getFlagsmithHasFeature('enable_metadata')
const error = createError || updateError
const totalSegments = ProjectStore.getTotalSegments() ?? 0
@@ -763,7 +765,9 @@ const CreateSegment: FC = ({
Custom Fields}
+ tabLabel={
+ Custom Fields
+ }
>
{MetadataTab}
diff --git a/frontend/web/components/pages/CreateEnvironmentPage.js b/frontend/web/components/pages/CreateEnvironmentPage.js
index dd6b49584c20..682031e7feb5 100644
--- a/frontend/web/components/pages/CreateEnvironmentPage.js
+++ b/frontend/web/components/pages/CreateEnvironmentPage.js
@@ -33,7 +33,10 @@ const CreateEnvironmentPage = class extends Component {
componentDidMount = () => {
API.trackPage(Constants.pages.CREATE_ENVIRONMENT)
- if (Utils.getFlagsmithHasFeature('enable_metadata')) {
+ if (
+ Utils.getPlansPermission('METADATA') &&
+ Utils.getFlagsmithHasFeature('enable_metadata')
+ ) {
getSupportedContentType(getStore(), {
organisation_id: AccountStore.getOrganisation().id,
}).then((res) => {
@@ -196,7 +199,8 @@ const CreateEnvironmentPage = class extends Component {
)}
- {Utils.getFlagsmithHasFeature('enable_metadata') &&
+ {Utils.getPlansPermission('METADATA') &&
+ Utils.getFlagsmithHasFeature('enable_metadata') &&
envContentType?.id && (
diff --git a/frontend/web/components/pages/EnvironmentSettingsPage.js b/frontend/web/components/pages/EnvironmentSettingsPage.js
index da8d8a1d5bea..26745fca1e6c 100644
--- a/frontend/web/components/pages/EnvironmentSettingsPage.js
+++ b/frontend/web/components/pages/EnvironmentSettingsPage.js
@@ -82,7 +82,10 @@ const EnvironmentSettingsPage = class extends Component {
})
})
- if (Utils.getFlagsmithHasFeature('enable_metadata')) {
+ if (
+ Utils.getPlansPermission('METADATA') &&
+ Utils.getFlagsmithHasFeature('enable_metadata')
+ ) {
getSupportedContentType(getStore(), {
organisation_id: AccountStore.getOrganisation().id,
}).then((res) => {
@@ -260,7 +263,9 @@ const EnvironmentSettingsPage = class extends Component {
},
} = this
const has4EyesPermission = Utils.getPlansPermission('4_EYES')
- const metadataEnable = Utils.getFlagsmithHasFeature('enable_metadata')
+ const metadataEnable =
+ Utils.getPlansPermission('METADATA') &&
+ Utils.getFlagsmithHasFeature('enable_metadata')
return (
diff --git a/frontend/web/components/pages/ProjectSettingsPage.js b/frontend/web/components/pages/ProjectSettingsPage.js
index 618c303396ff..eb667e32f8ce 100644
--- a/frontend/web/components/pages/ProjectSettingsPage.js
+++ b/frontend/web/components/pages/ProjectSettingsPage.js
@@ -167,7 +167,9 @@ const ProjectSettingsPage = class extends Component {
const { name, stale_flags_limit_days } = this.state
const hasStaleFlagsPermission = Utils.getPlansPermission('STALE_FLAGS')
- const metadataEnable = Utils.getFlagsmithHasFeature('enable_metadata')
+ const metadataEnable =
+ Utils.getPlansPermission('METADATA') &&
+ Utils.getFlagsmithHasFeature('enable_metadata')
return (
diff --git a/frontend/web/components/pages/SegmentsPage.tsx b/frontend/web/components/pages/SegmentsPage.tsx
index babf1112f391..8d5a9d7c1f04 100644
--- a/frontend/web/components/pages/SegmentsPage.tsx
+++ b/frontend/web/components/pages/SegmentsPage.tsx
@@ -24,7 +24,7 @@ import PageTitle from 'components/PageTitle'
import Switch from 'components/Switch'
import { setModalTitle } from 'components/modals/base/ModalDefault'
import classNames from 'classnames'
-import InfoMessage from 'components/InfoMessage';
+import InfoMessage from 'components/InfoMessage'
const CodeHelp = require('../../components/CodeHelp')
type SegmentsPageType = {