Skip to content

Commit

Permalink
fix: save feature error handling (#4058)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyle-ssg authored Aug 13, 2024
1 parent a6a0f91 commit 2517e9d
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 120 deletions.
254 changes: 135 additions & 119 deletions frontend/common/stores/feature-list-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,11 @@ const controller = {
}),
project_id: projectId,
})
.then((res) =>
Promise.all(
.then((res) => {
if (res.error) {
throw res.error?.error || res.error
}
return Promise.all(
(flag.multivariate_options || []).map((v) =>
data
.post(
Expand All @@ -107,8 +110,8 @@ const controller = {
data.get(
`${Project.api}projects/${projectId}/features/${res.data.id}/`,
),
),
)
)
})
.then(() =>
Promise.all([
data.get(`${Project.api}projects/${projectId}/features/`),
Expand Down Expand Up @@ -322,123 +325,132 @@ const controller = {

store.saving()
API.trackEvent(Constants.events.EDIT_FEATURE)
segmentOverridesProm.then(() => {
if (mode !== 'VALUE') {
prom = Promise.resolve()
} else if (environmentFlag) {
prom = data
.get(
`${Project.api}environments/${environmentId}/featurestates/${environmentFlag.id}/`,
)
.then((environmentFeatureStates) => {
const multivariate_feature_state_values =
environmentFeatureStates.multivariate_feature_state_values &&
environmentFeatureStates.multivariate_feature_state_values.map(
(v) => {
const matching =
environmentFlag.multivariate_feature_state_values.find(
(m) => m.id === v.multivariate_feature_option,
) || {}
return {
...v,
percentage_allocation:
matching.default_percentage_allocation,
}
},
)
environmentFlag.multivariate_feature_state_values =
multivariate_feature_state_values
return data.put(
segmentOverridesProm
.then(() => {
if (mode !== 'VALUE') {
prom = Promise.resolve()
} else if (environmentFlag) {
prom = data
.get(
`${Project.api}environments/${environmentId}/featurestates/${environmentFlag.id}/`,
Object.assign({}, environmentFlag, {
enabled: flag.default_enabled,
feature_state_value: Utils.getTypedValue(
flag.initial_value,
undefined,
true,
),
}),
)
})
} else {
prom = data.post(
`${Project.api}environments/${environmentId}/featurestates/`,
Object.assign({}, flag, {
enabled: false,
environment: environmentId,
feature: projectFlag,
}),
)
}

const segmentOverridesRequest =
mode === 'SEGMENT' && segmentOverrides
? (segmentOverrides.length
? updateSegmentPriorities(
getStore(),
segmentOverrides.map((override, index) => ({
id: override.id,
priority: index,
})),
.then((environmentFeatureStates) => {
const multivariate_feature_state_values =
environmentFeatureStates.multivariate_feature_state_values &&
environmentFeatureStates.multivariate_feature_state_values.map(
(v) => {
const matching =
environmentFlag.multivariate_feature_state_values.find(
(m) => m.id === v.multivariate_feature_option,
) || {}
return {
...v,
percentage_allocation:
matching.default_percentage_allocation,
}
},
)
: Promise.resolve([])
).then(() =>
Promise.all(
segmentOverrides.map((override) =>
data.put(
`${Project.api}features/featurestates/${override.feature_segment_value.id}/`,
{
...override.feature_segment_value,
enabled: override.enabled,
feature_state_value: Utils.valueToFeatureState(
override.value,
),
multivariate_feature_state_values:
override.multivariate_options &&
override.multivariate_options.map((o) => {
if (o.multivariate_feature_option) return o
return {
multivariate_feature_option:
environmentFlag.multivariate_feature_state_values[
o.multivariate_feature_option_index
].multivariate_feature_option,
percentage_allocation: o.percentage_allocation,
}
}),
},
environmentFlag.multivariate_feature_state_values =
multivariate_feature_state_values
return data.put(
`${Project.api}environments/${environmentId}/featurestates/${environmentFlag.id}/`,
Object.assign({}, environmentFlag, {
enabled: flag.default_enabled,
feature_state_value: Utils.getTypedValue(
flag.initial_value,
undefined,
true,
),
),
),
)
: Promise.resolve()

Promise.all([prom, segmentOverridesRequest]).then(([res, segmentRes]) => {
if (store.model) {
store.model.keyedEnvironmentFeatures[projectFlag.id] = res
if (segmentRes) {
const feature = _.find(
store.model.features,
(f) => f.id === projectFlag.id,
)
if (feature) {
feature.feature_segments = _.map(
segmentRes.feature_segments,
(segment) => ({
...segment,
segment: segment.segment.id,
}),
)
}
}
})
} else {
prom = data.post(
`${Project.api}environments/${environmentId}/featurestates/`,
Object.assign({}, flag, {
enabled: false,
environment: environmentId,
feature: projectFlag,
}),
)
}

if (store.model) {
store.model.lastSaved = new Date().valueOf()
}
onComplete && onComplete()
store.saved({})
const segmentOverridesRequest =
mode === 'SEGMENT' && segmentOverrides
? (segmentOverrides.length
? updateSegmentPriorities(
getStore(),
segmentOverrides.map((override, index) => ({
id: override.id,
priority: index,
})),
)
: Promise.resolve([])
).then(() =>
Promise.all(
segmentOverrides.map((override) =>
data.put(
`${Project.api}features/featurestates/${override.feature_segment_value.id}/`,
{
...override.feature_segment_value,
enabled: override.enabled,
feature_state_value: Utils.valueToFeatureState(
override.value,
),
multivariate_feature_state_values:
override.multivariate_options &&
override.multivariate_options.map((o) => {
if (o.multivariate_feature_option) return o
return {
multivariate_feature_option:
environmentFlag
.multivariate_feature_state_values[
o.multivariate_feature_option_index
].multivariate_feature_option,
percentage_allocation: o.percentage_allocation,
}
}),
},
),
),
),
)
: Promise.resolve()

Promise.all([prom, segmentOverridesRequest])
.then(([res, segmentRes]) => {
if (store.model) {
store.model.keyedEnvironmentFeatures[projectFlag.id] = res
if (segmentRes) {
const feature = _.find(
store.model.features,
(f) => f.id === projectFlag.id,
)
if (feature) {
feature.feature_segments = _.map(
segmentRes.feature_segments,
(segment) => ({
...segment,
segment: segment.segment.id,
}),
)
}
}
}

if (store.model) {
store.model.lastSaved = new Date().valueOf()
}
onComplete && onComplete()
store.saved({})
})
.catch((e) => {
API.ajaxHandler(store, e)
})
})
.catch((e) => {
API.ajaxHandler(store, e)
})
})
},
editFeatureStateChangeRequest: async (
projectId: string,
Expand Down Expand Up @@ -770,13 +782,17 @@ const controller = {
)
}

prom.then((res) => {
if (store.model) {
store.model.lastSaved = new Date().valueOf()
}
onComplete && onComplete()
store.saved({})
})
prom
.then((res) => {
if (store.model) {
store.model.lastSaved = new Date().valueOf()
}
onComplete && onComplete()
store.saved({})
})
.catch((e) => {
API.ajaxHandler(store, e)
})
},
getFeatureUsage(projectId, environmentId, flag, period) {
data
Expand Down
4 changes: 3 additions & 1 deletion frontend/web/components/ErrorMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ export default class ErrorMessage extends PureComponent {
<span className='icon-alert'>
<Icon name='close-circle' />
</span>
{typeof error === 'object' ? (
{error instanceof Error ? (
error.message
) : typeof error === 'object' ? (
<div
dangerouslySetInnerHTML={{
__html: Object.keys(error)
Expand Down
5 changes: 5 additions & 0 deletions frontend/web/project/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ global.API = {
}

// Catch coding errors that end up here
if (typeof res === 'string') {
store.error = new Error(res)
store.goneABitWest()
return
}
if (res instanceof Error) {
console.error(res)
store.error = res
Expand Down

0 comments on commit 2517e9d

Please sign in to comment.