-
Notifications
You must be signed in to change notification settings - Fork 40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FeatureFlag の実装 #4362
base: master
Are you sure you want to change the base?
FeatureFlag の実装 #4362
Changes from all commits
956cd2d
03278a2
0462f73
d63ac6b
61599d1
d029815
6ee01ab
2a3c4c3
ceeaff1
a53baf1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<template> | ||
<div :class="$style.container"> | ||
<div :class="$style.description"> | ||
<h3>{{ props.title }}</h3> | ||
<p>{{ props.description }}</p> | ||
<h4 v-if="FlagStatus['flag_test']">ていきょうしゅうりょうび</h4> | ||
<h4 v-else>提供終了日</h4> | ||
<p> | ||
{{ props.endAt.toLocaleDateString() }} | ||
</p> | ||
</div> | ||
<div> | ||
<a-toggle v-model="value" :disabled="props.endAt < new Date()" /> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts" setup> | ||
import AToggle from '/@/components/UI/AToggle.vue' | ||
import { useModelValueSyncer } from '/@/composables/useModelSyncer' | ||
import { useFeatureFlagSettings } from '/@/store/app/featureFlagSettings' | ||
|
||
const { FlagStatus } = useFeatureFlagSettings() | ||
|
||
const props = defineProps<{ | ||
title: string | ||
description: string | ||
modelValue: boolean | ||
endAt: Date | ||
}>() | ||
|
||
const emit = defineEmits<{ | ||
(e: 'update:modelValue', _val: boolean): void | ||
}>() | ||
|
||
const value = useModelValueSyncer(props, emit) | ||
</script> | ||
|
||
<style lang="scss" module> | ||
.container { | ||
display: flex; | ||
align-items: center; | ||
justify-content: space-between; | ||
gap: 1.5rem; | ||
} | ||
|
||
.description { | ||
display: flex; | ||
flex-direction: column; | ||
gap: 0.25rem; | ||
} | ||
</style> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import { defineStore, acceptHMRUpdate } from 'pinia' | ||
import { computed } from 'vue' | ||
import { convertToRefsStore } from '/@/store/utils/convertToRefsStore' | ||
import useIndexedDbValue from '/@/composables/utils/useIndexedDbValue' | ||
import { getVuexData } from '/@/store/utils/migrateFromVuex' | ||
import { isObjectAndHasKey } from '/@/lib/basic/object' | ||
import { promisifyRequest } from 'idb-keyval' | ||
|
||
type FeatureFlagKey = 'flag_test' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. やらなくてもいいのですが、featureFlagDescriptions を as const satisfies ... にして、そこから型をこねる (keyof typeof featureFlagDescriptions) と FeatureFlagKey という型は自動で導出できそうです |
||
|
||
type State = { | ||
status: Map<FeatureFlagKey, boolean | undefined> | ||
} | ||
|
||
type FeatureFlagDescription = { | ||
title: string | ||
description: string | ||
defaultValue: boolean | ||
endAt: Date // 最大一ヶ月先を指定し、それ以上先は指定しない | ||
} | ||
|
||
export type FeatureFlag = FeatureFlagDescription & { enabled: boolean } | ||
|
||
/* | ||
*** FeatureFlag の利用仮ルール *** | ||
- 日時の指定は最大一ヶ月先まで (FeatureFlag の利用を前提とした・FeatureFlag を乱用した運用を避けるため。期間後に本実装するかを検討する) | ||
- 利用しなくなった FeatureFlag は削除する | ||
- 仮ルールなので必要に応じて変えてほしいです | ||
*/ | ||
|
||
export const featureFlagDescriptions: Record< | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ここをMapにしてしまえば |
||
FeatureFlagKey, | ||
FeatureFlagDescription | ||
> = { | ||
flag_test: { | ||
title: 'フラグテスト・サンプル用', | ||
description: '「提供終了日」の表記がひらがなになります。', | ||
defaultValue: true, | ||
endAt: new Date('2024-08-31') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. これだとUTCになっちゃいそうですね (まぁ問題ないと言えばないが) |
||
} | ||
} | ||
|
||
const useFlagSettingsPinia = defineStore('app/featureFlagSettings', () => { | ||
const initialValue: State = { | ||
status: new Map<FeatureFlagKey, boolean | undefined>() | ||
} | ||
|
||
const [state, restoring, restoringPromise] = useIndexedDbValue( | ||
'store/app/featureFlagSettings', | ||
1, | ||
{ | ||
// migrate from vuex | ||
1: async getStore => { | ||
const vuexStore = await getVuexData() | ||
if (!vuexStore) return | ||
if (!isObjectAndHasKey(vuexStore, 'app')) return | ||
if (!isObjectAndHasKey(vuexStore.app, 'featureFlagSettings')) return | ||
const addReq = getStore().add(vuexStore.app.featureFlagSettings, 'key') | ||
await promisifyRequest(addReq) | ||
} | ||
}, | ||
initialValue | ||
) | ||
|
||
const isFlagEnabled = (flag: FeatureFlagKey): boolean => { | ||
const featureFlag = featureFlagDescriptions[flag] | ||
if (featureFlag.endAt < new Date()) { | ||
return false | ||
} | ||
return state.status.get(flag) ?? featureFlag.defaultValue | ||
} | ||
|
||
const FeatureFlags = computed(() => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. この関数は |
||
const res: Map<FeatureFlagKey, FeatureFlag> = new Map() | ||
Object.entries(featureFlagDescriptions).forEach(([flag, featureFlag]) => { | ||
res.set(flag as FeatureFlagKey, { | ||
title: featureFlag.title, | ||
description: featureFlag.description, | ||
defaultValue: featureFlag.defaultValue, | ||
endAt: featureFlag.endAt, | ||
enabled: isFlagEnabled(flag as FeatureFlagKey) | ||
}) | ||
}) | ||
return res | ||
}) | ||
|
||
const FlagStatus = computed(() => { | ||
const res: Record<FeatureFlagKey, boolean> = {} as Record< | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 使う時はRecordの方が使いやすいと思うので、Recordに統一するのが良さそうに見えます |
||
FeatureFlagKey, | ||
boolean | ||
> | ||
Object.entries(featureFlagDescriptions).forEach(([flag, featureFlag]) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 細かいですけど、 |
||
res[flag as FeatureFlagKey] = isFlagEnabled(flag as FeatureFlagKey) | ||
}) | ||
return res | ||
}) | ||
|
||
const updateFeatureFlagStatus = async ( | ||
flag: FeatureFlagKey, | ||
enabled: boolean | ||
) => { | ||
await restoringPromise.value | ||
state.status.set(flag, enabled) | ||
} | ||
|
||
return { | ||
updateFeatureFlagStatus, | ||
FeatureFlags, | ||
FlagStatus, | ||
restoring | ||
} | ||
}) | ||
|
||
export const useFeatureFlagSettings = convertToRefsStore(useFlagSettingsPinia) | ||
|
||
if (import.meta.hot) { | ||
import.meta.hot.accept(acceptHMRUpdate(useFlagSettingsPinia, import.meta.hot)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<template> | ||
<section :class="$style.featureFlagTab"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
<div v-if="!restoring"> | ||
<feature-flag | ||
v-for="[key, item] in FeatureFlags" | ||
:key="key" | ||
:title="item.title" | ||
:description="item.description" | ||
:model-value="FlagStatus[key]" | ||
:end-at="item.endAt" | ||
@update:model-value="(v: boolean) => updateFeatureFlagStatus(key, v)" | ||
/> | ||
</div> | ||
<div v-else> | ||
<p>Now loading...</p> | ||
</div> | ||
</section> | ||
</template> | ||
|
||
<script lang="ts" setup> | ||
import { useFeatureFlagSettings } from '/@/store/app/featureFlagSettings' | ||
import FeatureFlag from '/@/components/Settings/FeatureFlagTab/FeatureFlag.vue' | ||
|
||
const { updateFeatureFlagStatus, FeatureFlags, FlagStatus, restoring } = | ||
useFeatureFlagSettings() | ||
</script> | ||
|
||
<style lang="scss" module> | ||
.featureFlagTab { | ||
display: flex; | ||
flex-direction: column; | ||
gap: 1.5rem; | ||
} | ||
</style> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
細かいですが、提供終了日は横に並べた方が見やすいかもしれません
ではなく
みたいにするということです