From 5dcd39f564a3b06f23c0dab42dece0165027ebc5 Mon Sep 17 00:00:00 2001 From: James Wampler Date: Wed, 17 Jun 2026 12:45:45 -0700 Subject: [PATCH] Fix identity trait add and persistence bugs Remove Key from UpsertTraitRequest body (comes from URL route). Emit 'updated' event from IdentityDetail on trait mutations and sync parent. Reload segment data on tab activation and dialog open. --- .../src/components/identities/IdentityDetail.vue | 14 ++++++++++++++ src/admin/src/views/IdentitiesView.vue | 7 +++++++ .../MicCheck.Api/Identities/UpsertTraitRequest.cs | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/admin/src/components/identities/IdentityDetail.vue b/src/admin/src/components/identities/IdentityDetail.vue index 9f52225..76c8494 100755 --- a/src/admin/src/components/identities/IdentityDetail.vue +++ b/src/admin/src/components/identities/IdentityDetail.vue @@ -320,6 +320,7 @@ const props = defineProps(); const emit = defineEmits<{ 'update:modelValue': [value: boolean]; deleted: [identityId: number]; + updated: [identity: IdentityResponse]; }>(); const contextStore = useContextStore(); @@ -420,6 +421,10 @@ watch( { immediate: true }, ); +watch(activeTab, (tab) => { + if (tab === 'segments') loadSegmentsData(); +}); + async function onToggleOverride(override: FeatureStateResponse, enabled: boolean | null): Promise { if (enabled === null || !props.identity) return; const envApiKey = contextStore.currentEnvironment?.apiKey; @@ -489,6 +494,12 @@ async function onAddOverride(): Promise { } } +function emitTraitsUpdated(): void { + if (props.identity) { + emit('updated', { ...props.identity, traits: [...editedTraits.value] }); + } +} + async function onSaveTrait(key: string, value: string): Promise { if (!props.identity) return; const envApiKey = contextStore.currentEnvironment?.apiKey; @@ -499,6 +510,7 @@ async function onSaveTrait(key: string, value: string): Promise { await upsertIdentityTrait(envApiKey, props.identity.id, key, { value }); const idx = editedTraits.value.findIndex((t: TraitResponse) => t.key === key); if (idx >= 0) editedTraits.value[idx] = { key, value }; + emitTraitsUpdated(); } catch { traitsError.value = 'Failed to save trait. Please try again.'; } finally { @@ -515,6 +527,7 @@ async function onDeleteTrait(key: string): Promise { try { await deleteIdentityTrait(envApiKey, props.identity.id, key); editedTraits.value = editedTraits.value.filter((t: TraitResponse) => t.key !== key); + emitTraitsUpdated(); } catch { traitsError.value = 'Failed to delete trait. Please try again.'; } finally { @@ -540,6 +553,7 @@ async function onAddTrait(): Promise { } newTraitKey.value = ''; newTraitValue.value = ''; + emitTraitsUpdated(); } catch { traitsError.value = 'Failed to add trait. Please try again.'; } finally { diff --git a/src/admin/src/views/IdentitiesView.vue b/src/admin/src/views/IdentitiesView.vue index 6d1779b..07094bd 100755 --- a/src/admin/src/views/IdentitiesView.vue +++ b/src/admin/src/views/IdentitiesView.vue @@ -91,6 +91,7 @@ v-model="showDetail" :identity="selectedIdentity" @deleted="onIdentityDeleted" + @updated="onIdentityUpdated" /> @@ -246,6 +247,12 @@ function onCreateDialogClosed(): void { createError.value = null; } +function onIdentityUpdated(updated: IdentityResponse): void { + const idx = identities.value.findIndex((i) => i.id === updated.id); + if (idx >= 0) identities.value[idx] = updated; + selectedIdentity.value = updated; +} + function onIdentityDeleted(identityId: number): void { identities.value = identities.value.filter((i) => i.id !== identityId); showDetail.value = false; diff --git a/src/api/MicCheck.Api/Identities/UpsertTraitRequest.cs b/src/api/MicCheck.Api/Identities/UpsertTraitRequest.cs index 5dfa49d..60b9a32 100755 --- a/src/api/MicCheck.Api/Identities/UpsertTraitRequest.cs +++ b/src/api/MicCheck.Api/Identities/UpsertTraitRequest.cs @@ -1,3 +1,3 @@ namespace MicCheck.Api.Identities; -public record UpsertTraitRequest(string Key, string Value); +public record UpsertTraitRequest(string Value);