Skip to content
Closed
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ jobs:
- name: Run Task
run: |
export PYTHONPATH=$(pwd)
FLASK_ENV=development uv run flask -A server.app --debug app version
uv run ${{ matrix.task.command }}

- name: Minimize uv cache
Expand Down
12 changes: 12 additions & 0 deletions configs/server.config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ contributor = "{repository_name}投稿ユーザー"
# It should include '{repository_name}' placeholder.
general_user = "{repository_name}一般ユーザー"

[users]
# List of fields to include in the exported user details.
export_fields = [
"id",
"user_name",
"groups[].id",
"groups[].name",
"role",
"edu_person_principal_names[]",
"preferred_language",
"emails[]",
]

[map_core]
# Base URL for the mAP Core service.
Expand Down
91 changes: 72 additions & 19 deletions src/app/components/bulk/BulkResultStep.vue
Original file line number Diff line number Diff line change
@@ -1,38 +1,54 @@
<script setup lang="ts">
const properties = defineProps<{
historyId: string
taskId: string
taskId?: string
}>()

const {
uploadResult,
useBulkIndicators,
fetchUploadResult,
} = useExecuteUpload()
const { makePageInfo } = useBulk()
const { query, makePageInfo, makeIndicators } = useBulk()

const toast = useToast()
const url = `/api/bulk/result/${properties.historyId}`

const { handleFetchError } = useErrorHandling()
const { data: status, execute: executeStatus }
= await useFetch<BulkProcessingStatus>(`/api/bulk/execute/status/${properties.taskId}`)
= await useFetch<BulkProcessingStatus>(`/api/bulk/execute/status/${properties.taskId}`,
{
method: 'GET',
lazy: true,
immediate: false,
onResponseError({ response }) {
switch (response.status) {
case 404: {
break
}
default:{
handleFetchError({ response })
break
}
}
},
server: false })

const { polling: { interval, maxAttempts } } = useAppConfig()
const isPolling = ref(false)
const pollExecuteStatus = async () => {
isPolling.value = true
for (let index = 0; index < maxAttempts; index++) {
await executeStatus()
const st = (status.value?.status)
if (st === 'SUCCESS') return
if (st === 'SUCCESS') {
isPolling.value = false
return
}
if (st === 'FAILURE') {
toast.add({
title: $t('bulk.status.error'),
description: $t('bulk.execute.failed'),
color: 'error',
icon: 'i-lucide-circle-x',
})
isPolling.value = false
return
}
const { uploadResult: result } = await fetchUploadResult(url)
uploadResult.value = result.value
await new Promise(r => setTimeout(r, interval))
}
toast.add({
Expand All @@ -41,14 +57,50 @@ const pollExecuteStatus = async () => {
color: 'error',
icon: 'i-lucide-circle-x',
})
isPolling.value = false
}

const { data: executeResult, execute: fetchExecuteResult, status: getResultStatus }
= await useFetch<ExecuteResults>(`/api/bulk/result/${properties.historyId}`, {
method: 'GET',
query,
lazy: true,
server: false,
immediate: false,
onResponseError({ response }) {
switch (response.status) {
case 403: {
showError({
status: 403,
statusText: 'Forbidden',
message: $t('error-page.forbidden.bulk-result'),
})
break
}
case 404: {
showError({
status: 404,
statusText: 'Not Found',
message: $t('error-page.not-found.bulk-result'),
})
break
}
default: {
handleFetchError({ response })
break
}
}
},
})
onMounted(async () => {
await pollExecuteStatus()
if (properties.taskId)
await pollExecuteStatus()
await fetchExecuteResult()
})

const indicators = useBulkIndicators
const fileInfo = computed(() => uploadResult.value!.fileInfo)
const pageInfo = makePageInfo(uploadResult)
const indicators = computed(() => makeIndicators(executeResult.value))
const fileInfo = computed(() => executeResult.value!.fileInfo)
const pageInfo = computed(() => makePageInfo(executeResult))
</script>

<template>
Expand Down Expand Up @@ -95,11 +147,12 @@ const pageInfo = makePageInfo(uploadResult)
</div>

<BulkUserTable
:data="uploadResult!.results"
:total-count="uploadResult!.total"
:data="executeResult!.results"
:total-count="executeResult!.total"
:page-info="pageInfo"
:offset="uploadResult!.offset"
:offset="executeResult!.offset"
:title="$t('bulk.import-results')"
:status="isPolling ? 'loading' : getResultStatus "
/>
</div>
</template>
67 changes: 50 additions & 17 deletions src/app/components/bulk/BulkUploadStep.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
<script setup lang="ts">
import type { FormError, FormSubmitEvent } from '@nuxt/ui'
import type { FormError, FormErrorEvent, FormSubmitEvent } from '@nuxt/ui'

const emit = defineEmits<{
next: [taskId: string]
next: [data: BulkProcessingStatus]
error: [event: FormErrorEvent]
update: [repositoryId: string]
}>()

const { isProcessing } = useUserUpload()
const { table: { pageSize } } = useAppConfig()

const state = reactive<{
repository: string | undefined
file: File | undefined
Expand Down Expand Up @@ -57,21 +60,44 @@ const handleNext = async (event: FormSubmitEvent<Schema>) => {
const formData = new FormData()
formData.append('bulk_file', event.data.file!)
formData.append('repository_id', event.data.repository!)

const { data } = await useFetch<BulkProcessingStatus>('/api/bulk/upload-file', {
method: 'POST',
body: formData,
onResponseError({ response }) {
handleFetchError({ response })
},
server: false,
})

if (data.value?.taskId) {
emit('next', data.value.taskId)
try {
const data = await $fetch<BulkProcessingStatus>('/api/bulk/upload-file', {
method: 'POST',
body: formData,
onResponseError({ response }) {
switch (response.status) {
case 403: {
showError({
status: 403,
statusText: 'Forbidden',
message: $t('error-page.forbidden.bulk-edit'),
})
break
}
case 404: {
showError({
status: 404,
statusText: 'Not Found',
message: $t('error-page.not-found.repository'),
})
break
}
default: {
handleFetchError({ response })
}
}
isProcessing.value = false
},
})

if (data) {
emit('next', data)
}
}
catch {
// Error is handled in onResponseError
}
}

const repositorySelect = useTemplateRef('repositorySelect')
const {
items: repositoryNames,
Expand All @@ -88,6 +114,12 @@ const {
}),
})
setupRepoScroll(repositorySelect)

const { handleFormError } = useFormError()
const onError = (event: FormErrorEvent) => {
handleFormError(event)
emit('error', event)
}
</script>

<template>
Expand All @@ -112,8 +144,9 @@ setupRepoScroll(repositorySelect)
</template>
</UAlert>
<UForm
class="space-y-6"
:validate="validateFileFormat" :state="state"
@submit="handleNext"
@submit="handleNext" @error="onError"
>
<UFormField
:label="$t('bulk.select-repository')"
Expand All @@ -126,7 +159,7 @@ setupRepoScroll(repositorySelect)
:placeholder="$t('group.placeholder.repository')"
:items="repositoryNames" :loading="repoSearchStatus === 'pending'" ignore-filter
:ui="{ base: 'w-full' }"
@update:open="onRepoOpen"
@update:open="onRepoOpen" @update:model-value="emit('update', state.repository!)"
/>
</UFormField>

Expand Down
15 changes: 12 additions & 3 deletions src/app/components/bulk/BulkUserTable.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<script setup lang="ts" generic="T extends UploadResult | ValidationResult">
<script setup lang="ts">
defineProps<{
data: T[]
data: EachResult[]
totalCount: number
title?: string
pageInfo?: string
offset?: number
status: string
}>()

const { updateQuery, pageNumber, pageSize, makeStatusFilters, columns } = useBulk()
Expand Down Expand Up @@ -43,8 +44,16 @@ const filterSelects = makeStatusFilters()
<UTable
:data="data"
:columns="columns"
:loading="status === 'loading'"
sticky
/>
>
<template #empty>
<UEmpty
icon="i-lucide-search-x"
:title="$t('bulk.no-results')"
/>
</template>
</UTable>

<div class="flex items-center">
<div class="flex items-center gap-4 flex-1">
Expand Down
Loading
Loading