diff --git a/locales/en.yml b/locales/en.yml
index e535bfcf91..87ec8d7560 100644
--- a/locales/en.yml
+++ b/locales/en.yml
@@ -4,6 +4,7 @@ Current: Current
Filters: Filters
Override: Override
Storage: Storage
+ab-testing-progressive-deploy-conflict: AB testing and progressive deploy cannot be enabled at the same time
account: Account
account-error: Error while updating your account
account-password-error: Error happened, please try again
@@ -101,6 +102,7 @@ channel-link-fail: Cannot override the channel something wrong happened
channel-linked: Channel override setted
channel-linking: Link to channel
channel-make-now: Make default
+channel-progressive-deploy: Enable progressive deploy
channels: Channels
check-email: Please check your email and verify
check-on-web: Check on website
@@ -136,6 +138,7 @@ 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
+disable-progressive-deploy: Disabled progressive deploy
discord: Discord
discover-module-in-a: Discover module in Awesome-capacitor
discover-your-dashbo: Discover your dashboard !
@@ -145,6 +148,7 @@ download: Download
email: Email
email-address: Email address
enabled-ab-testing: Enabled AB testing
+enabled-progressive-deploy: Enabled progressive deploy
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
@@ -154,6 +158,7 @@ failed: Failed
filters: Filters
first-name: First name
force-version: Force version
+force-version-change: Skip progressive deploy
forgot: Forgot
forgot-check-email: Check your email to get the link to reset your password
forgot-success: Password updated successfully
@@ -191,6 +196,7 @@ login-to-your-accoun: Login to your account
logout: Logout
logs: Logs
lowerCaseError: Should contain lowercase letters
+main-bundle-number: Main bundle number
make-normal: Make normal
making-this-channel-: >-
Making this channel "normal" will need you to configure an other channel
@@ -254,6 +260,10 @@ please-upgrade: Please upgrade your plan to add more users
plugin-version: Plugin version
prediction: Prediction
pro-tip-you-can-copy: 'Pro tip: you can copy the'
+progressive-bundle-number: Progressive bundle number
+progressive-deploy-option: Select progressive deploy option
+progressive-deploy-set-percentage: You cannot set secondary version percentage when progressive deploy is enabled
+progressive-percentage: Progressive deploy status
projection: Projections
recommended: Recommended
recommended-for-you: Recommended for you
@@ -288,7 +298,10 @@ size-not-found: Not found
something-went-wrong-try-again-later: Something went wrong! Try again later.
special-api-access: Special API access
specialError: Should contain special characters
+start-new-deploy: Start new progressive deploy
start-using-capgo: 'Start using:'
+status-complete: complete
+status-failed: failed
stored-externally: stored externally
submit: Submit
support: Support
diff --git a/src/pages/app/p/[p]/bundle/[bundle].vue b/src/pages/app/p/[p]/bundle/[bundle].vue
index 57b8c8b38c..c2448b9546 100644
--- a/src/pages/app/p/[p]/bundle/[bundle].vue
+++ b/src/pages/app/p/[p]/bundle/[bundle].vue
@@ -115,6 +115,28 @@ async function setSecondChannel(channel: Database['public']['Tables']['channels'
.eq('id', channel.id)
}
+async function setChannelProgressive(channel: Database['public']['Tables']['channels']['Row'], id: number) {
+ return supabase
+ .from('channels')
+ .update({
+ secondVersion: id,
+ version: channel.secondVersion,
+ secondaryVersionPercentage: 0.1,
+ })
+ .eq('id', channel.id)
+}
+
+async function setChannelSkipProgressive(channel: Database['public']['Tables']['channels']['Row'], id: number) {
+ return supabase
+ .from('channels')
+ .update({
+ secondVersion: id,
+ version: id,
+ secondaryVersionPercentage: 1,
+ })
+ .eq('id', channel.id)
+}
+
async function ASChannelChooser() {
if (!version.value)
return
@@ -172,14 +194,14 @@ async function ASChannelChooser() {
for (const chan of channels.value) {
const v: number = chan.version as any
- if (!chan.enableAbTesting) {
+ if (!chan.enableAbTesting && !chan.enable_progressive_deploy) {
buttons.push({
text: chan.name,
selected: version.value.id === v,
handler: async () => { await normalHandler(chan) },
})
}
- else {
+ else if (chan.enableAbTesting && !chan.enable_progressive_deploy) {
buttons.push({
text: `${chan.name}-A`,
selected: version.value.id === v,
@@ -197,6 +219,63 @@ async function ASChannelChooser() {
},
})
}
+ else {
+ buttons.push({
+ text: `${chan.name}`,
+ selected: version.value.id === chan.secondVersion,
+ handler: async () => {
+ const newButtons = []
+ newButtons.push({
+ text: t('start-new-deploy'),
+ selected: false,
+ handler: async () => {
+ if (!version.value)
+ return
+
+ try {
+ await setChannelProgressive(chan, version.value.id)
+ await getChannels()
+ }
+ catch (error) {
+ console.error(error)
+ toast.error(t('cannot-test-app-some'))
+ }
+ },
+ })
+
+ newButtons.push({
+ text: t('force-version-change'),
+ selected: false,
+ handler: async () => {
+ if (!version.value)
+ return
+ try {
+ await setChannelSkipProgressive(chan, version.value.id)
+ await getChannels()
+ }
+ catch (error) {
+ console.error(error)
+ toast.error(t('cannot-test-app-some'))
+ }
+ },
+ })
+
+ newButtons.push({
+ text: t('button-cancel'),
+ role: 'cancel',
+ handler: () => {
+ // console.log('Cancel clicked')
+ },
+ })
+
+ displayStore.actionSheetOption = {
+ header: t('progressive-deploy-option'),
+ buttons: newButtons,
+ }
+ displayStore.showActionSheet = true
+ },
+ })
+ }
}
buttons.push({
text: t('button-cancel'),
@@ -395,7 +474,7 @@ function hideString(str: string) {
-
+
diff --git a/src/pages/app/p/[p]/channel/[channel].vue b/src/pages/app/p/[p]/channel/[channel].vue
index 15e3a30f6d..0a94e27490 100644
--- a/src/pages/app/p/[p]/channel/[channel].vue
+++ b/src/pages/app/p/[p]/channel/[channel].vue
@@ -125,6 +125,7 @@ async function getChannel() {
android,
updated_at,
enableAbTesting,
+ enable_progressive_deploy,
secondaryVersionPercentage,
secondVersion (
name,
@@ -277,6 +278,11 @@ async function enableAbTesting() {
const val = !channel.value.enableAbTesting
+ if (val && channel.value.enable_progressive_deploy) {
+ toast.error(t('ab-testing-progressive-deploy-conflict'))
+ return
+ }
+
const { error } = await supabase
.from('channels')
.update({ enableAbTesting: val })
@@ -291,6 +297,33 @@ async function enableAbTesting() {
}
}
+async function enableProgressiveDeploy() {
+ if (!channel.value)
+ return
+
+ const val = !channel.value.enable_progressive_deploy
+
+ if (val && channel.value.enableAbTesting) {
+ toast.error(t('ab-testing-progressive-deploy-conflict'))
+ return
+ }
+
+ const { error } = await supabase
+ .from('channels')
+ .update({ enable_progressive_deploy: val, secondVersion: val ? channel.value.version.id : undefined })
+ .eq('id', id.value)
+
+ if (error) {
+ console.error(error)
+ }
+ else {
+ channel.value.enable_progressive_deploy = val
+ toast.success(val ? t('enabled-progressive-deploy') : t('disable-progressive-deploy'))
+ }
+
+ await reload()
+}
+
const debouncedSetSecondaryVersionPercentage = debounce (async (percentage: number) => {
const { error } = await supabase
.from('channels')
@@ -301,10 +334,24 @@ const debouncedSetSecondaryVersionPercentage = debounce (async (percentage: numb
console.error(error)
}, 500, { leading: true, trailing: true, maxWait: 500 })
+const debouncedInformAboutProgressiveDeployPercentageSet = debounce(() => {
+ toast.error(t('progressive-deploy-set-percentage'))
+}, 500, { leading: true, trailing: true, maxWait: 500 })
+
async function setSecondaryVersionPercentage(percentage: number) {
+ if (channel.value?.enable_progressive_deploy)
+ return
+
secondaryVersionPercentage.value = percentage
await debouncedSetSecondaryVersionPercentage(percentage)
}
+
+function onMouseDownSecondaryVersionSlider(event: MouseEvent) {
+ if (channel.value?.enable_progressive_deploy) {
+ debouncedInformAboutProgressiveDeployPercentageSet()
+ event.preventDefault()
+ }
+}
@@ -315,11 +362,16 @@ async function setSecondaryVersionPercentage(percentage: number) {
-
-
+
+
+
+
+
+
+
@@ -421,6 +473,16 @@ async function setSecondaryVersionPercentage(percentage: number) {
/>
+
+
+
+
+
(setSecondaryVersionPercentage(parseInt(e.target.value, 10)))"
+ @mousedown="onMouseDownSecondaryVersionSlider"
/>
diff --git a/src/types/supabase.types.ts b/src/types/supabase.types.ts
index bd85c82cf8..be9c84d54b 100644
--- a/src/types/supabase.types.ts
+++ b/src/types/supabase.types.ts
@@ -416,13 +416,14 @@ export interface Database {
created_by: string
disableAutoUpdateToMajor: boolean
disableAutoUpdateUnderNative: boolean
+ enable_progressive_deploy: boolean
enableAbTesting: boolean
id: number
ios: boolean
name: string
public: boolean
secondaryVersionPercentage: number
- secondVersion: number | null
+ secondVersion: number
updated_at: string
version: number
}
@@ -437,15 +438,16 @@ export interface Database {
created_by: string
disableAutoUpdateToMajor?: boolean
disableAutoUpdateUnderNative?: boolean
+ enable_progressive_deploy?: boolean
enableAbTesting?: boolean
id?: number
ios?: boolean
name: string
public?: boolean
secondaryVersionPercentage?: number
- secondVersion?: number | null
+ secondVersion?: number
updated_at?: string
- version: number
+ version?: number
}
Update: {
allow_dev?: boolean
@@ -458,13 +460,14 @@ export interface Database {
created_by?: string
disableAutoUpdateToMajor?: boolean
disableAutoUpdateUnderNative?: boolean
+ enable_progressive_deploy?: boolean
enableAbTesting?: boolean
id?: number
ios?: boolean
name?: string
public?: boolean
secondaryVersionPercentage?: number
- secondVersion?: number | null
+ secondVersion?: number
updated_at?: string
version?: number
}
@@ -1163,6 +1166,10 @@ export interface Database {
}
Returns: number
}
+ delete_user: {
+ Args: {}
+ Returns: void
+ },
convert_bytes_to_mb: {
Args: {
byt: number
diff --git a/supabase/functions/_utils/supabase.types.ts b/supabase/functions/_utils/supabase.types.ts
index b2f23758d9..06b471f8e3 100644
--- a/supabase/functions/_utils/supabase.types.ts
+++ b/supabase/functions/_utils/supabase.types.ts
@@ -416,7 +416,8 @@ export interface Database {
created_by: string
disableAutoUpdateToMajor: boolean
disableAutoUpdateUnderNative: boolean
- enableAbTesting: boolean
+ enableAbTesting: boolean,
+ enable_progressive_deploy?: boolean
id: number
ios: boolean
name: string
@@ -438,6 +439,7 @@ export interface Database {
disableAutoUpdateToMajor?: boolean
disableAutoUpdateUnderNative?: boolean
enableAbTesting?: boolean
+ enable_progressive_deploy?: boolean
id?: number
ios?: boolean
name: string
@@ -459,6 +461,7 @@ export interface Database {
disableAutoUpdateToMajor?: boolean
disableAutoUpdateUnderNative?: boolean
enableAbTesting?: boolean
+ enable_progressive_deploy?: boolean
id?: number
ios?: boolean
name?: string
diff --git a/supabase/functions/on_version_update/index.ts b/supabase/functions/on_version_update/index.ts
index 1c89167798..e54b525237 100644
--- a/supabase/functions/on_version_update/index.ts
+++ b/supabase/functions/on_version_update/index.ts
@@ -22,7 +22,7 @@ async function isUpdate(body: UpdatePayload<'app_versions'>) {
return sendRes()
}
const exist = await r2.checkIfExist(record.bucket_id)
- console.log('exist ?', record.app_id, record.bucket_id, v2Path, exist)
+ console.log('exist ?', record.app_id, record.bucket_id, exist)
if (!exist && !record.bucket_id.endsWith('.zip')) {
console.log('upload to r2', record.bucket_id)
// upload to r2
diff --git a/supabase/functions/stats/index.ts b/supabase/functions/stats/index.ts
index 908678ee95..84ba71a158 100644
--- a/supabase/functions/stats/index.ts
+++ b/supabase/functions/stats/index.ts
@@ -65,7 +65,8 @@ async function main(url: URL, headers: BaseHeaders, method: string, body: AppSta
if (coerce)
version_build = coerce.version
- version_name = (version_name === 'builtin' || !version_name) ? version_build : version_name
+ console.log(`VERSION NAME: ${version_name}`)
+ version_name = !version_name ? version_build : version_name
const device: Database['public']['Tables']['devices']['Insert'] = {
platform: platform as Database['public']['Enums']['platform_os'],
device_id,
@@ -91,10 +92,9 @@ async function main(url: URL, headers: BaseHeaders, method: string, body: AppSta
.from('app_versions')
.select('id, user_id')
.eq('app_id', app_id)
- .or(`name.eq.${version_name},name.eq.builtin`)
- .order('id', { ascending: false })
- .limit(1)
+ .or(`name.eq.${version_name}`)
.single()
+ console.log(`appVersion ${JSON.stringify(appVersion)}`)
if (appVersion) {
stat.version = appVersion.id
device.version = appVersion.id
@@ -125,6 +125,7 @@ async function main(url: URL, headers: BaseHeaders, method: string, body: AppSta
// }
}
else if (failActions.includes(action)) {
+ console.log('FAIL!')
const sent = await sendNotif('user:update_fail', {
current_app_id: app_id,
current_device_id: device_id,
diff --git a/supabase/functions/updates/index.ts b/supabase/functions/updates/index.ts
index 01824614da..3b600462e3 100644
--- a/supabase/functions/updates/index.ts
+++ b/supabase/functions/updates/index.ts
@@ -159,6 +159,7 @@ async function main(url: URL, headers: BaseHeaders, method: string, body: AppInf
external_url
),
secondaryVersionPercentage,
+ enable_progressive_deploy,
enableAbTesting,
version (
id,
@@ -240,8 +241,13 @@ async function main(url: URL, headers: BaseHeaders, method: string, body: AppInf
}, 200)
}
let enableAbTesting: boolean = devicesOverride?.version || (channelOverride?.channel_id as any)?.enableAbTesting || channelData?.enableAbTesting
+
+ const enableProgressiveDeploy: boolean = devicesOverride?.version || (channelOverride?.channel_id as any)?.enableProgressiveDeploy || channelData?.enable_progressive_deploy
+ const enableSecondVersion = enableAbTesting || enableProgressiveDeploy
+
const version: Database['public']['Tables']['app_versions']['Row'] = devicesOverride?.version || (channelOverride?.channel_id as any)?.version || channelData?.version
- const secondVersion: Database['public']['Tables']['app_versions']['Row'] | undefined = (devicesOverride?.version || undefined || (channelData?.enableAbTesting ? channelData?.secondVersion : undefined)) as any as Database['public']['Tables']['app_versions']['Row'] | undefined
+ const secondVersion: Database['public']['Tables']['app_versions']['Row'] | undefined = (devicesOverride?.version || undefined || (enableSecondVersion ? channelData?.secondVersion : undefined)) as any as Database['public']['Tables']['app_versions']['Row'] | undefined
+
const planValid = await isAllowedAction(appOwner.user_id)
await checkPlan(appOwner.user_id)
const versionId = versionData ? versionData.id : version.id
@@ -251,13 +257,13 @@ async function main(url: URL, headers: BaseHeaders, method: string, body: AppInf
const ip = xForwardedFor.split(',')[1]
console.log('IP', ip)
- if (enableAbTesting) {
- console.log(secondVersion)
+ if (enableAbTesting || enableProgressiveDeploy) {
if (secondVersion && secondVersion?.name !== 'unknown') {
+ const secondVersionPercentage: number = (devicesOverride?.version || (channelOverride?.channel_id as any)?.secondaryVersionPercentage || channelData?.secondaryVersionPercentage) ?? 0
// eslint-disable-next-line max-statements-per-line
- if (secondVersion.name === version_name || version.name === 'unknown') { version = secondVersion }
+ if (secondVersion.name === version_name || version.name === 'unknown' || secondVersionPercentage === 1) { version = secondVersion }
+ else if (secondVersionPercentage === 0) { /* empty (do nothing) */ }
else if (version.name !== version_name) {
- const secondVersionPercentage: number = (devicesOverride?.version || (channelOverride?.channel_id as any)?.secondaryVersionPercentage || channelData?.secondaryVersionPercentage) ?? 0
const randomChange = Math.random()
if (randomChange < secondVersionPercentage)
@@ -397,7 +403,7 @@ async function main(url: URL, headers: BaseHeaders, method: string, body: AppInf
// console.log(id, 'save stats', device_id)
await sendStats('get', platform, device_id, app_id, version_build, versionId)
// check signedURL and if it's url
- if (!signedURL || signedURL.startsWith('http://') || !signedURL.startsWith('https://')) {
+ if (!signedURL && (!signedURL.startsWith('http://') || !signedURL.startsWith('https://'))) {
console.log(id, 'Cannot get bundle signedURL', signedURL, app_id)
await sendStats('cannotGetBundle', platform, device_id, app_id, version_build, versionId)
return sendRes({
diff --git a/supabase/migrations/20230815171919_base.sql b/supabase/migrations/20230815171919_base.sql
index 0a381186f3..27eb2e3cc2 100644
--- a/supabase/migrations/20230815171919_base.sql
+++ b/supabase/migrations/20230815171919_base.sql
@@ -1052,6 +1052,7 @@ CREATE TABLE "public"."channels" (
"public" boolean DEFAULT false NOT NULL,
"disableAutoUpdateUnderNative" boolean DEFAULT true NOT NULL,
"enableAbTesting" boolean not null default false,
+ "enable_progressive_deploy" boolean not null default false,
"secondaryVersionPercentage" double precision not null default '0'::double precision,
"secondVersion" bigint not null default '1883'::bigint,
"disableAutoUpdateToMajor" boolean DEFAULT true NOT NULL,
diff --git a/supabase/seed.sql b/supabase/seed.sql
index b6d2da7ce4..4c980bc6bc 100644
--- a/supabase/seed.sql
+++ b/supabase/seed.sql
@@ -122,4 +122,11 @@ SELECT http_set_curlopt(''CURLOPT_TIMEOUT_MS'', ''15000'');
AND apps.retention > 0
AND extract(epoch from now()) - extract(epoch from app_versions_meta.created_at) > apps.retention
AND extract(epoch from now()) - extract(epoch from app_versions_meta.updated_at) > apps.retention
- ', 'localhost', 5432, 'postgres', 'supabase_admin', 't', 'cron_everyday_retention');
+ ', 'localhost', 5432, 'postgres', 'supabase_admin', 't', 'cron_everyday_retention'),
+(20, '*/10 * * * *', '
+ update channels
+ SET
+ "secondaryVersionPercentage" = CASE WHEN channels."secondVersion" not in (select version from stats where stats.action=''update_fail'' and 10800 > extract(epoch from now()) - extract(epoch from stats.created_at)) THEN "secondaryVersionPercentage" + 0.1 ELSE 0 END
+ where channels.enable_progressive_deploy = true
+ and channels."secondaryVersionPercentage" between 0 AND 0.9;
+ ', 'localhost', 5432, 'postgres', 'supabase_admin', 't', 'cron_progressive_deploy');