Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@ try {
}
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}
}
4 changes: 4 additions & 0 deletions locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ cannot-test-app-some: Cannot test app something wrong happened
change: Change
changed-password-suc: Password changed successfully
channel: Channel
channel-ab-testing: Enable AB testing
channel-ab-testing-percentage: Percentage of users recving secondary version
channel-create: Create channel
channel-deleted: Channel deleted
channel-invit: Type email to invite
Expand Down Expand Up @@ -131,6 +133,7 @@ device: Device
device-id: Device ID
devices: Devices
devices-using-this-b: Devices using this bundle
disable-ab-testing: Disabled AB testing
disable-auto-downgra: Disable auto downgrade under native
disable-auto-upgrade: Disable auto upgrade above major
discord: Discord
Expand All @@ -141,6 +144,7 @@ dont-have-an-account: Don’t have an account?
download: Download
email: Email
email-address: Email address
enabled-ab-testing: Enabled AB testing
encrypted: Encrypted bundles
enter-your-email-add: Enter your email address and we'll send you a link to reset your password.
enter-your-new-passw: Enter your new password and confirm
Expand Down
4 changes: 4 additions & 0 deletions locales/pl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ Filters: Filtry
Override: Nadpisanie
Storage: Składowanie
account: Konto
channel-ab-testing: Włącz testowanie AB
channel-ab-testing-percentage: Procent użytkowników którzy otrzymają drugą wersję
disable-ab-testing: Wyłączono testy AB
enabled-ab-testing: Włączono testy AB
account-error: Błąd podczas aktualizowania konta
account-password-error: Wystąpił błąd, spróbuj ponownie
account-password-heading: Zmień moje hasło
Expand Down
115 changes: 96 additions & 19 deletions src/pages/app/p/[p]/bundle/[bundle].vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const version = ref<Database['public']['Tables']['app_versions']['Row']>()
const channels = ref<(Database['public']['Tables']['channels']['Row'])[]>([])
const channel = ref<(Database['public']['Tables']['channels']['Row'])>()
const version_meta = ref<Database['public']['Tables']['app_versions_meta']['Row']>()
const secondaryChannel = ref<boolean>(false)

async function copyToast(text: string) {
copy(text)
Expand Down Expand Up @@ -62,8 +63,10 @@ async function getChannels() {
// search if the bundle is used in a channel
channels.value.forEach((chan) => {
const v: number = chan.version as any
if (version.value && v === version.value.id)
if (version.value && (v === version.value.id || version.value.id === chan.secondVersion)) {
channel.value = chan
secondaryChannel.value = (version.value.id === chan.secondVersion)
}
})
}

Expand Down Expand Up @@ -103,28 +106,97 @@ async function setChannel(channel: Database['public']['Tables']['channels']['Row
.eq('id', channel.id)
}

async function setSecondChannel(channel: Database['public']['Tables']['channels']['Row'], id: number) {
return supabase
.from('channels')
.update({
secondVersion: id,
})
.eq('id', channel.id)
}

async function ASChannelChooser() {
if (!version.value)
return
const buttons = []

// This makes sure that A and B cannot be selected on the same time
const commonAbHandler = async (chan: Database['public']['Tables']['channels']['Row'] | undefined, ab: 'a' | 'b') => {
if (!chan)
return

const aSelected = version?.value?.id === (chan.version as any)
const bSelected = version?.value?.id === (chan.secondVersion as any)

if (aSelected && ab === 'b') {
const id = await getUnknowBundleId()
if (!id)
return

setChannel(chan, id)
}
else if (bSelected && ab === 'a') {
const id = await getUnknowBundleId()
if (!id)
return

setSecondChannel(chan, id)
}
}

const normalHandler = async (chan: Database['public']['Tables']['channels']['Row']) => {
if (!version.value)
return
try {
await setChannel(chan, version.value.id)
await getChannels()
}
catch (error) {
console.error(error)
toast.error(t('cannot-test-app-some'))
}
}

const secondHandler = async (chan: Database['public']['Tables']['channels']['Row']) => {
if (!version.value)
return
try {
await setSecondChannel(chan, version.value.id)
await getChannels()
}
catch (error) {
console.error(error)
toast.error(t('cannot-test-app-some'))
}
}

for (const chan of channels.value) {
const v: number = chan.version as any
buttons.push({
text: chan.name,
selected: version.value.id === v,
handler: async () => {
if (!version.value)
return
try {
await setChannel(chan, version.value.id)
await getChannels()
}
catch (error) {
console.error(error)
toast.error(t('cannot-test-app-some'))
}
},
})
if (!chan.enableAbTesting) {
buttons.push({
text: chan.name,
selected: version.value.id === v,
handler: async () => { await normalHandler(chan) },
})
}
else {
buttons.push({
text: `${chan.name}-A`,
selected: version.value.id === v,
handler: async () => {
await commonAbHandler(channel.value, 'a')
await normalHandler(chan)
},
})
buttons.push({
text: `${chan.name}-B`,
selected: version.value.id === chan.secondVersion,
handler: async () => {
await commonAbHandler(channel.value, 'b')
await secondHandler(chan)
},
})
}
}
buttons.push({
text: t('button-cancel'),
Expand Down Expand Up @@ -181,7 +253,11 @@ async function openChannel() {
const id = await getUnknowBundleId()
if (!id)
return
await setChannel(channel.value, id)
if (!secondaryChannel.value)
await setChannel(channel.value, id)
else
await setSecondChannel(channel.value, id)

await getChannels()
}
catch (error) {
Expand Down Expand Up @@ -319,7 +395,8 @@ function hideString(str: string) {
<InfoRow v-if="version_meta?.uninstalls" :label="t('uninstall')" :value="version_meta.uninstalls.toLocaleString()" />
<InfoRow v-if="version_meta?.fails" :label="t('fail')" :value="version_meta.fails.toLocaleString()" />
<!-- <InfoRow v-if="version_meta?.installs && version_meta?.fails" :label="t('percent-fail')" :value="failPercent" /> -->
<InfoRow :label="t('channel')" :value="channel ? channel.name : t('set-bundle')" :is-link="true" @click="openChannel()" />
<InfoRow v-if="channel" :label="t('channel')" :value="channel!.enableAbTesting ? (secondaryChannel ? `${channel!.name}-B` : `${channel!.name}-A`) : channel!.name" :is-link="true" @click="openChannel()" />
<InfoRow v-else :label="t('channel')" :value="t('set-bundle')" :is-link="true" @click="openChannel()" />
<!-- session_key -->
<InfoRow v-if="version.session_key" :label="t('session_key')" :value="hideString(version.session_key)" :is-link="true" @click="copyToast(version?.session_key || '')" />
<!-- version.external_url -->
Expand Down
92 changes: 89 additions & 3 deletions src/pages/app/p/[p]/channel/[channel].vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import {
kList, kListItem,
kRange,
kToggle,
} from 'konsta/vue'
import { toast } from 'vue-sonner'
import debounce from 'lodash.debounce'
import { useSupabase } from '~/services/supabase'
import { formatDate } from '~/services/date'
import { useMainStore } from '~/stores/main'
Expand All @@ -21,6 +23,7 @@ import { urlToAppId } from '~/services/conversion'

interface Channel {
version: Database['public']['Tables']['app_versions']['Row']
secondVersion: Database['public']['Tables']['app_versions']['Row']
}
const router = useRouter()
const displayStore = useDisplayStore()
Expand All @@ -34,6 +37,7 @@ const loading = ref(true)
const deviceIds = ref<string[]>([])
const channel = ref<Database['public']['Tables']['channels']['Row'] & Channel>()
const ActiveTab = ref('info')
const secondaryVersionPercentage = ref(50)

const tabs: Tab[] = [
{
Expand All @@ -60,10 +64,21 @@ const tabs: Tab[] = [
function openBundle() {
if (!channel.value)
return
if (channel.value.version.name === 'unknown')
return
console.log('openBundle', channel.value.version.id)
router.push(`/app/p/${route.params.p}/bundle/${channel.value.version.id}`)
}

function openSecondBundle() {
if (!channel.value)
return
if (channel.value.secondVersion.name === 'unknown')
return
console.log('openBundle', channel.value.version.id)
router.push(`/app/p/${route.params.p}/bundle/${channel.value.secondVersion.id}`)
}

async function getDeviceIds() {
if (!channel.value)
return
Expand Down Expand Up @@ -108,15 +123,26 @@ async function getChannel() {
disableAutoUpdateToMajor,
ios,
android,
updated_at
updated_at,
enableAbTesting,
secondaryVersionPercentage,
secondVersion (
name,
id
)
`)
.eq('id', id.value)
.single()
if (error) {
console.error('no channel', error)
return
}
channel.value = data as Database['public']['Tables']['channels']['Row'] & Channel

channel.value = data as unknown as Database['public']['Tables']['channels']['Row'] & Channel
secondaryVersionPercentage.value = (data.secondaryVersionPercentage * 100) | 0

// Conversion of type '{ id: number; name: string; public: boolean; version: { id: unknown; name: unknown; app_id: unknown; bucket_id: unknown; created_at: unknown; }[]; created_at: string; allow_emulator: boolean; allow_dev: boolean; allow_device_self_set: boolean; ... 7 more ...; secondVersion: number | null; }' to type '{ allow_dev: boolean; allow_device_self_set: boolean; allow_emulator: boolean; android: boolean; app_id: string; beta: boolean; created_at: string; created_by: string; disableAutoUpdateToMajor: boolean; ... 9 more ...; version: number; } & Channel' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
// Type '{ id: number; name: string; public: boolean; version: { id: unknown; name: unknown; app_id: unknown; bucket_id: unknown; created_at: unknown; }[]; created_at: string; allow_emulator: boolean; allow_dev: boolean; allow_device_self_set: boolean; ... 7 more ...; secondVersion: number | null; }' is missing the following properties from type '{ allow_dev: boolean; allow_device_self_set: boolean; allow_emulator: boolean; android: boolean; app_id: string; beta: boolean; created_at: string; created_by: string; disableAutoUpdateToMajor: boolean; ... 9 more ...; version: number; }': app_id, beta, created_byts(2352)
}
catch (error) {
console.error(error)
Expand Down Expand Up @@ -244,6 +270,41 @@ async function openPannel() {
}
displayStore.showActionSheet = true
}

async function enableAbTesting() {
if (!channel.value)
return

const val = !channel.value.enableAbTesting

const { error } = await supabase
.from('channels')
.update({ enableAbTesting: val })
.eq('id', id.value)

if (error) {
console.error(error)
}
else {
channel.value.enableAbTesting = val
toast.success(val ? t('enabled-ab-testing') : t('disable-ab-testing'))
}
}

const debouncedSetSecondaryVersionPercentage = debounce (async (percentage: number) => {
const { error } = await supabase
.from('channels')
.update({ secondaryVersionPercentage: percentage / 100 })
.eq('id', id.value)

if (error)
console.error(error)
}, 500, { leading: true, trailing: true, maxWait: 500 })

async function setSecondaryVersionPercentage(percentage: number) {
secondaryVersionPercentage.value = percentage
await debouncedSetSecondaryVersionPercentage(percentage)
}
</script>

<template>
Expand All @@ -254,7 +315,11 @@ async function openPannel() {
<dl class="divide-y divide-gray-500">
<InfoRow :label="t('name')" :value="channel.name" />
<!-- Bundle Number -->
<InfoRow :label="t('bundle-number')" :value="channel.version.name" :is-link="true" @click="openBundle" />
<InfoRow v-if="!channel.enableAbTesting" :label="t('bundle-number')" :value="channel.version.name" :is-link="true" @click="channel.version.name !== 'unknown' ? openBundle : ''" />
<template v-else>
<InfoRow :label="`${t('bundle-number')} A`" :value="channel.version.name" :is-link="true" @click="openBundle" />
<InfoRow :label="`${t('bundle-number')} B`" :value="channel.secondVersion.name" :is-link="true" @click="openSecondBundle" />
</template>
<!-- Created At -->
<InfoRow :label="t('created-at')" :value="formatDate(channel.created_at)" />
<!-- Last Update -->
Expand Down Expand Up @@ -346,6 +411,27 @@ async function openPannel() {
/>
</template>
</k-list-item>
<k-list-item label :title="t('channel-ab-testing')" class="text-lg text-gray-700 dark:text-gray-200">
<template #after>
<k-toggle
class="-my-1 k-color-success"
component="div"
:checked="channel?.enableAbTesting"
@change="enableAbTesting()"
/>
</template>
</k-list-item>
<k-list-item label :title="`${t('channel-ab-testing-percentage')}: ${secondaryVersionPercentage}%`" class="text-lg text-gray-700 dark:text-gray-200">
<template #after>
<k-range
:value="secondaryVersionPercentage"
class="-my-1 k-color-success"
component="div"
:step="5"
@input="(e: any) => (setSecondaryVersionPercentage(parseInt(e.target.value, 10)))"
/>
</template>
</k-list-item>
<k-list-item label :title="t('unlink-bundle')" class="text-lg text-red-500" link @click="openPannel" />
</k-list>
</dl>
Expand Down
Loading