Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
2c815ed
feat: add bulk user upload
nobuyoshikosaka Feb 4, 2026
7a01ae0
fix: bulk view translation and add openpyxl library
nobuyoshikosaka Feb 5, 2026
4fd2a5c
fix: lint error and refactoring long function
nobuyoshikosaka Feb 5, 2026
c25ea13
Merge remote-tracking branch 'upstream/develop' into feat
nobuyoshikosaka Feb 12, 2026
d547bf3
Merge remote-tracking branch 'upstream/develop' into feat
nobuyoshikosaka Feb 12, 2026
0905446
Merge remote-tracking branch 'upstream/develop' into feat
nobuyoshikosaka Feb 13, 2026
f14e794
feat: add storage configuration
ivis-kuroda Feb 13, 2026
b4e378b
fix: refactor bulk view and config for make directory
nobuyoshikosaka Feb 16, 2026
a79f6b8
fix: current_user for celery task
nobuyoshikosaka Feb 16, 2026
303dc40
update: add validation for repository creation
ivis-kuroda Feb 17, 2026
8a567da
feat: available to update repository
ivis-kuroda Feb 17, 2026
667f8cf
Merge remote-tracking branch 'kuroda/develop' into feat
nobuyoshikosaka Feb 17, 2026
0a18e01
fix: refactor history view
nobuyoshikosaka Feb 17, 2026
1b12e4a
fix: refactor bulk user upload components and services
nobuyoshikosaka Feb 17, 2026
efeea3b
fix: typecheck and lint
nobuyoshikosaka Feb 17, 2026
0692370
feat: add favicon
nobuyoshikosaka Feb 17, 2026
5dadbf8
fix: bulk error handling and add celery-types
nobuyoshikosaka Feb 17, 2026
167449b
fix: typecheck for celery
nobuyoshikosaka Feb 17, 2026
3ea888f
fix: add login user id config for devlogin
nobuyoshikosaka Feb 17, 2026
94a9d78
fix: history sort column
nobuyoshikosaka Feb 18, 2026
79fd95c
fix: decode login date from bytes in refresh_session
nobuyoshikosaka Feb 18, 2026
c083657
Merge remote-tracking branch 'upstream/develop' into feat
nobuyoshikosaka Feb 19, 2026
49736f0
fix: fix config storage directory for permission error and add test c…
nobuyoshikosaka Feb 19, 2026
e150ce7
fix: lint warning and type error
nobuyoshikosaka Feb 19, 2026
088f50c
fix: history view
nobuyoshikosaka Feb 20, 2026
74c98f1
fix: bulk request body for endpoint from users to groups
nobuyoshikosaka Feb 24, 2026
4d1b17f
fix: refresh_session for config
nobuyoshikosaka Feb 26, 2026
c17d9b2
fix: sql for jsonb column
nobuyoshikosaka Feb 26, 2026
6b76621
Merge remote-tracking branch 'origin/develop' into feat
nobuyoshikosaka Feb 26, 2026
f416229
fix: correct missing reference updates after function move
nobuyoshikosaka Feb 26, 2026
a0b52e0
Merge branch 'feat' into unit-test
nobuyoshikosaka Feb 27, 2026
8421838
test: add unit test for auth
nobuyoshikosaka Feb 27, 2026
308942b
test: add unit test for search_queries
nobuyoshikosaka Feb 27, 2026
5846ccb
test: add unit tests for absolute session refresh and invalid TTL
nobuyoshikosaka Feb 27, 2026
0bd9fe2
test: add unit test for history module
nobuyoshikosaka Feb 27, 2026
872eda1
Merge remote-tracking branch 'origin/develop' into unit-test
nobuyoshikosaka Mar 2, 2026
47db9a3
test: fix group ID pattern in config and search unit test
nobuyoshikosaka Mar 2, 2026
6cb83e9
test: add unit tests for transformers and refactor imports
nobuyoshikosaka Mar 2, 2026
0c5ab9d
test: add unit test for bulk module
nobuyoshikosaka Mar 2, 2026
b57aa7b
fix: fix lint error
nobuyoshikosaka Mar 2, 2026
cdf0ac2
Merge branch 'develop' into unit-test
nobuyoshikosaka Mar 3, 2026
3811201
fix: resolve mistakes after merge conflict resolution
nobuyoshikosaka Mar 3, 2026
399c6c9
fix: history module and bulks module
nobuyoshikosaka Mar 4, 2026
0141982
Merge remote-tracking branch 'origin/develop' into unit-test
nobuyoshikosaka Mar 4, 2026
173fe23
Merge remote-tracking branch 'kuroda/develop' into unit-test
nobuyoshikosaka Mar 5, 2026
b56cb01
test: fix unit test for merge
nobuyoshikosaka Mar 5, 2026
6fb236d
test: add unit test for client of bulk request
nobuyoshikosaka Mar 5, 2026
1842c5e
fix: group filter logic for user
nobuyoshikosaka Mar 5, 2026
cedd88d
test: fix unit test for search filter
nobuyoshikosaka Mar 5, 2026
d0c0a68
Merge branch 'develop' into fix
nobuyoshikosaka Mar 7, 2026
b6a4e89
Merge remote-tracking branch 'upstream/develop' into fix
nobuyoshikosaka Mar 9, 2026
a2fec70
fix: bulk and history view and add log message
nobuyoshikosaka Mar 9, 2026
13545c8
Merge remote-tracking branch 'upstream/develop' into fix
nobuyoshikosaka Mar 9, 2026
c4c4719
feat: show version command
ivis-kuroda Mar 9, 2026
9e5abc6
ci: generate stub before typecheck
nobuyoshikosaka Mar 9, 2026
ebd013f
Merge remote-tracking branch 'upstream/develop' into unit-test
nobuyoshikosaka Mar 9, 2026
b939ba2
Merge commit '9e5abc6e7dd3525f58285ceeaa20e1a237ed9fb1' into unit-test
nobuyoshikosaka Mar 9, 2026
e632882
feat: features config
nobuyoshikosaka Mar 10, 2026
cfc9b30
fix: remove dump
nobuyoshikosaka Mar 10, 2026
8ad11ad
Merge commit 'cfc9b3004215bedfa3d2bad52504ccfb2b217bc4' into unit-test
nobuyoshikosaka Mar 10, 2026
22663b6
fix: logmessage format
nobuyoshikosaka Mar 10, 2026
079882c
fix: end of sentence for localize
nobuyoshikosaka Mar 10, 2026
7cda79c
fix: logmessage format
nobuyoshikosaka Mar 10, 2026
67ae211
Merge commit 'c9994230e32bc79202423c3087a7343d90bb70f8' into unit-test
nobuyoshikosaka Mar 10, 2026
f6a182c
fix: file export api
nobuyoshikosaka Mar 10, 2026
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
47 changes: 47 additions & 0 deletions configs/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,47 @@ const wayf = {
] as { name: string, entityID: string }[],
}

/**
* These are due to temporary constraints
* in the future, all features will be enabled and the settings will be deleted.
*/
const features = {
/** repositories feature flags */
repositories: {
/**
* If true, the search will be performed on the server side.
* If false, the search will be performed on the client (browser) side.
*/
'server-search': false,

/** Whether to allow sorting columns */
'sort-columns': false,
},
/** groups feature flags */
groups: {
/** Whether to allow sorting columns */
'sort-columns': false,
},
/** users feature flags */
users: {
/** Whether to allow filters to be applied at the same time for both roles and groups */
'filter-by-both-role-group': false,
/** Whether to allow the last modified filter */
'filter-by-last-modified': false,
/** Whether to allow the search by username only */
'search-only-username': true,
/** Whether to allow sorting columns */
'sort-columns': false,
/** Whether to display file upload button */
'file-upload': false,
},
/** history feature flags */
history: {
/** Whether to display history upload tab */
upload: false,
},
}

export default {
/** Server hostname of this application */
serverName,
Expand All @@ -192,4 +233,10 @@ export default {
polling,
/** WAYF (Embedded DS) configuration */
wayf,
/**
* Feature flags configuration. \
* These are due to temporary constraints
* in the future, all features will be enabled and the settings will be deleted.
*/
features,
}
24 changes: 24 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 Expand Up @@ -237,3 +249,15 @@ url = "amqp://guest:guest@rabbitmq:5672//"

# user name of the developer account.
# user_name = ""


# Feature flags for enabling or disabling application features.
# These are due to temporary constraints
# in the future, all features will be enabled and the settings will be deleted.
[features]
# Whether user search by user name only in users.
# If false, Enable search by username, email, or ePPN.
search_only_username = true

# Whether mAP Core API bulk operation is enabled or disabled.
enable_bulk_operation = false
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