feat: MigrateService script setup #2432

Merged
konrad merged 1 commits from dpschen/frontend:feature/feat-MigrationService-script-setup into main 2022-11-03 14:19:44 +00:00
12 changed files with 105 additions and 111 deletions

View File

@ -31,8 +31,8 @@ import ListTeamsComponent from '../views/teams/ListTeams.vue'
import ListLabelsComponent from '../views/labels/ListLabels.vue'
import NewLabelComponent from '../views/labels/NewLabel.vue'
// Migration
import MigrationComponent from '../views/migrator/Migrate.vue'
import MigrateServiceComponent from '../views/migrator/MigrateService.vue'
const MigrationComponent = () => import('@/views/migrate/Migration.vue')
const MigrationHandlerComponent = () => import('@/views/migrate/MigrationHandler.vue')
// List Views
import ListList from '../views/list/ListList.vue'
const ListGantt = () => import('../views/list/ListGantt.vue')
@ -445,7 +445,11 @@ const router = createRouter({
{
path: '/migrate/:service',
name: 'migrate.service',
component: MigrateServiceComponent,
component: MigrationHandlerComponent,
props: route => ({
service: route.params.service as string,
code: route.params.code as string,
}),
},
{
path: '/filters/new',

View File

@ -1,11 +1,13 @@
import AbstractService from '../abstractService'
export type MigrationConfig = { code: string }
// This service builds on top of the abstract service and basically just hides away method names.
// It enables migration services to be created with minimal overhead and even better method names.
export default class AbstractMigrationService extends AbstractService {
export default class AbstractMigrationService extends AbstractService<MigrationConfig> {
serviceUrlKey = ''
constructor(serviceUrlKey) {
constructor(serviceUrlKey: string) {
super({
update: '/migration/' + serviceUrlKey + '/migrate',
})
@ -20,7 +22,7 @@ export default class AbstractMigrationService extends AbstractService {
return this.getM('/migration/' + this.serviceUrlKey + '/status')
}
migrate(data) {
migrate(data: MigrationConfig) {
return this.update(data)
}
}

View File

@ -1,4 +1,3 @@
import type {IFile} from '@/modelTypes/IFile'
import AbstractService from '../abstractService'
// This service builds on top of the abstract service and basically just hides away method names.
@ -21,7 +20,7 @@ export default class AbstractMigrationFileService extends AbstractService {
return false
}
migrate(file: IFile) {
migrate(file: File) {
return this.uploadFile(
this.paths.create,
file,

View File

@ -14,9 +14,9 @@
type="file"
/>
<x-button
:loading="migrationService.loading"
:disabled="migrationService.loading || undefined"
@click="$refs.uploadInput.click()"
:loading="migrationFileService.loading"
:disabled="migrationFileService.loading || undefined"
@click="uploadInput?.click()"
>
{{ $t('migrate.upload') }}
</x-button>
@ -57,129 +57,118 @@
</div>
</div>
<div v-else>
<message class="mb-4">
<Message class="mb-4">
{{ message }}
</message>
</Message>
<x-button :to="{name: 'home'}">{{ $t('misc.refresh') }}</x-button>
</div>
</div>
</template>
<script lang="ts">
import {defineComponent} from 'vue'
import AbstractMigrationService from '@/services/migrator/abstractMigration'
import AbstractMigrationFileService from '@/services/migrator/abstractMigrationFile'
import Logo from '@/assets/logo.svg?component'
import Message from '@/components/misc/message.vue'
import { setTitle } from '@/helpers/setTitle'
import {formatDateLong} from '@/helpers/time/formatDate'
import {MIGRATORS} from './migrators'
import { useNamespaceStore } from '@/stores/namespaces'
const PROGRESS_DOTS_COUNT = 8
export default defineComponent({
name: 'MigrateService',
components: {
Logo,
Message,
},
data() {
return {
progressDotsCount: PROGRESS_DOTS_COUNT,
authUrl: '',
isMigrating: false,
lastMigrationDate: null,
message: '',
migratorAuthCode: '',
migrationService: null,
}
},
computed: {
migrator() {
return MIGRATORS[this.$route.params.service]
},
},
export default {
beforeRouteEnter(to) {
if (MIGRATORS[to.params.service] === undefined) {
if (MIGRATORS[to.params.service as string] === undefined) {
return {name: 'not-found'}
}
},
}
</script>
created() {
this.initMigration()
},
<script setup lang="ts">
import {computed, ref, shallowReactive} from 'vue'
import {useI18n} from 'vue-i18n'
mounted() {
setTitle(this.$t('migrate.titleService', {name: this.migrator.name}))
},
import Logo from '@/assets/logo.svg?component'
import Message from '@/components/misc/message.vue'
methods: {
formatDateLong,
import AbstractMigrationService, { type MigrationConfig } from '@/services/migrator/abstractMigration'
import AbstractMigrationFileService from '@/services/migrator/abstractMigrationFile'
async initMigration() {
this.migrationService = this.migrator.isFileMigrator
? new AbstractMigrationFileService(this.migrator.id)
: new AbstractMigrationService(this.migrator.id)
import {formatDateLong} from '@/helpers/time/formatDate'
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
if (this.migrator.isFileMigrator) {
return
}
import {MIGRATORS} from './migrators'
import {useNamespaceStore} from '@/stores/namespaces'
import {useTitle} from '@/composables/useTitle'
this.authUrl = await this.migrationService.getAuthUrl().then(({url}) => url)
const PROGRESS_DOTS_COUNT = 8
this.migratorAuthCode = location.hash.startsWith('#token=')
? location.hash.substring(7)
: this.$route.query.code
const props = defineProps<{
service: string,
code?: string,
}>()
if (!this.migratorAuthCode) {
return
}
const {time} = await this.migrationService.getStatus()
if (time) {
this.lastMigrationDate = typeof time === 'string' && time?.startsWith('0001-')
? null
: new Date(time)
const {t} = useI18n({useScope: 'global'})
if (this.lastMigrationDate) {
return
}
}
await this.migrate()
},
const progressDotsCount = ref(PROGRESS_DOTS_COUNT)
const authUrl = ref('')
const isMigrating = ref(false)
const lastMigrationDate = ref<Date | null>(null)
const message = ref('')
const migratorAuthCode = ref('')
async migrate() {
this.isMigrating = true
this.lastMigrationDate = null
this.message = ''
const migrator = computed(() => MIGRATORS[props.service])
let migrationConfig = {code: this.migratorAuthCode}
const migrationService = shallowReactive(new AbstractMigrationService(migrator.value.id))
const migrationFileService = shallowReactive(new AbstractMigrationFileService(migrator.value.id))
if (this.migrator.isFileMigrator) {
if (this.$refs.uploadInput.files.length === 0) {
return
}
migrationConfig = this.$refs.uploadInput.files[0]
}
useTitle(() => t('migrate.titleService', {name: migrator.value.name}))
try {
const {message} = await this.migrationService.migrate(migrationConfig)
this.message = message
const namespaceStore = useNamespaceStore()
return namespaceStore.loadNamespaces()
} finally {
this.isMigrating = false
}
},
},
})
async function initMigration() {
if (migrator.value.isFileMigrator) {
return
}
authUrl.value = await migrationService.getAuthUrl().then(({url}) => url)
const TOKEN_HASH_PREFIX = '#token='

It looks like this call to the api happens but the authUrl stays empty.

It looks like this call to the api happens but the `authUrl` stays empty.
migratorAuthCode.value = location.hash.startsWith(TOKEN_HASH_PREFIX)
? location.hash.substring(TOKEN_HASH_PREFIX.length)
: props.code as string
if (!migratorAuthCode.value) {
return
}
const {time} = await migrationService.getStatus()
if (time) {
lastMigrationDate.value = parseDateOrNull(time)
if (lastMigrationDate.value) {
return
}
}
await migrate()
}
initMigration()
const uploadInput = ref<HTMLInputElement | null>(null)
async function migrate() {
isMigrating.value = true
lastMigrationDate.value = null
message.value = ''
let migrationConfig: MigrationConfig | File = {code: migratorAuthCode.value}

@konrad:
This MigrationConfig here is wrong. Since the config is passed to the migrate method of the service which passes it to update, which wants a File (?) I'm not sure what type to define here. Any idea?

@konrad: This `MigrationConfig` here is wrong. Since the config is passed to the `migrate` method of the service which passes it to update, which wants a `File` (?) I'm not sure what type to define here. Any idea?

Maybe MigrationConfig | File? The File type is correct but only when the migrator requires a file to be uploaded. Otherwise it needs that code to make api calls to the third party api.

Maybe `MigrationConfig | File`? The `File` type is correct but only when the migrator requires a file to be uploaded. Otherwise it needs that code to make api calls to the third party api.

The update function is defined in the AbstractService thus it's types as well.
Have to find out how we can pass types there so that it supports different types depending on the use-case. I think it doesn't make sense to support the MigrationConfig type there.

The update function is defined in the `AbstractService` thus it's types as well. Have to find out how we can pass types there so that it supports different types depending on the use-case. I think it doesn't make sense to support the MigrationConfig type there.
if (migrator.value.isFileMigrator) {
if (uploadInput.value?.files?.length === 0) {
return
}
migrationConfig = uploadInput.value?.files?.[0] as File
}
try {
const result = migrator.value.isFileMigrator
? await migrationFileService.migrate(migrationConfig as File)
: await migrationService.migrate(migrationConfig as MigrationConfig)
message.value = result.message
const namespaceStore = useNamespaceStore()
return namespaceStore.loadNamespaces()
} finally {
isMigrating.value = false
}
}
</script>
<style lang="scss" scoped>

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 471 B

After

Width:  |  Height:  |  Size: 471 B

View File

Before

Width:  |  Height:  |  Size: 745 B

After

Width:  |  Height:  |  Size: 745 B

View File

Before

Width:  |  Height:  |  Size: 512 B

After

Width:  |  Height:  |  Size: 512 B

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -49,4 +49,4 @@ export const MIGRATORS: IMigratorRecord = {
icon: tickTickIcon as string,
isFileMigrator: true,
},
}
} as const