Skip to content

Commit 01ee590

Browse files
committed
Enable to bulk assign in frontend
1 parent 927ede4 commit 01ee590

File tree

4 files changed

+156
-1
lines changed

4 files changed

+156
-1
lines changed

frontend/components/example/ActionMenu.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
@create="$emit('create')"
66
@upload="$emit('upload')"
77
@download="$emit('download')"
8+
@assign="$emit('assign')"
89
/>
910
</template>
1011

1112
<script lang="ts">
1213
import Vue from 'vue'
13-
import { mdiUpload, mdiDownload } from '@mdi/js'
14+
import { mdiAccountCheck, mdiUpload, mdiDownload } from '@mdi/js'
1415
import ActionMenu from '~/components/utils/ActionMenu.vue'
1516
1617
export default Vue.extend({
@@ -30,6 +31,11 @@ export default Vue.extend({
3031
title: this.$t('dataset.exportDataset'),
3132
icon: mdiDownload,
3233
event: 'download'
34+
},
35+
{
36+
title: 'Assign to member',
37+
icon: mdiAccountCheck,
38+
event: 'assign'
3339
}
3440
]
3541
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<template>
2+
<v-card>
3+
<v-toolbar color="primary white--text" flat>
4+
<v-toolbar-title>Assign examples to members</v-toolbar-title>
5+
</v-toolbar>
6+
<v-card-text>
7+
<v-container fluid>
8+
<v-row>
9+
<v-card-title class="pb-0 pl-3">Select assignment strategy</v-card-title>
10+
<v-col cols="12">
11+
<v-select
12+
v-model="selectedStrategy"
13+
:items="strategies"
14+
item-text="displayName"
15+
item-value="value"
16+
outlined
17+
dense
18+
hide-details
19+
></v-select>
20+
{{ strategies.find((strategy) => strategy.value === selectedStrategy)?.description }}
21+
Note that assigning to an administrator does not make sense.
22+
</v-col>
23+
</v-row>
24+
<v-row>
25+
<v-card-title class="pb-0 pl-3">Allocate weights</v-card-title>
26+
<v-col v-for="(member, i) in members" :key="member.id" cols="12" class="pt-0 pb-0">
27+
<v-subheader class="pl-0">{{ member.username }}</v-subheader>
28+
<v-slider v-model="workloadAllocation[i]" :max="100" class="align-center">
29+
<template #append>
30+
<v-text-field
31+
v-model="workloadAllocation[i]"
32+
class="mt-0 pt-0"
33+
type="number"
34+
style="width: 60px"
35+
></v-text-field>
36+
</template>
37+
</v-slider>
38+
</v-col>
39+
</v-row>
40+
</v-container>
41+
</v-card-text>
42+
<v-card-actions>
43+
<v-spacer />
44+
<v-btn class="text-capitalize" text color="primary" data-test="cancel-button" @click="cancel">
45+
Cancel
46+
</v-btn>
47+
<v-btn
48+
class="text-none"
49+
text
50+
:disabled="!validateWeight"
51+
data-test="delete-button"
52+
@click="agree"
53+
>
54+
Assign
55+
</v-btn>
56+
</v-card-actions>
57+
</v-card>
58+
</template>
59+
60+
<script lang="ts">
61+
import Vue from 'vue'
62+
import { MemberItem } from '~/domain/models/member/member'
63+
64+
export default Vue.extend({
65+
data() {
66+
return {
67+
members: [] as MemberItem[],
68+
workloadAllocation: [] as number[],
69+
selectedStrategy: 'weighted_sequential'
70+
}
71+
},
72+
73+
async fetch() {
74+
this.members = await this.$repositories.member.list(this.projectId)
75+
this.workloadAllocation = this.members.map(() => Math.round(100 / this.members.length))
76+
},
77+
78+
computed: {
79+
projectId() {
80+
return this.$route.params.id
81+
},
82+
83+
strategies() {
84+
return [
85+
{
86+
displayName: 'Weighted sequential',
87+
value: 'weighted_sequential',
88+
description:
89+
'Assign examples to members in order of their workload. The total weight must equal 100.'
90+
},
91+
{
92+
displayName: 'Weighted random',
93+
value: 'weighted_random',
94+
description:
95+
'Assign examples to members randomly based on their workload. The total weight must equal 100.'
96+
},
97+
{
98+
displayName: 'Sampling without replacement',
99+
value: 'sampling_without_replacement',
100+
description: 'Assign examples to members randomly without replacement.'
101+
}
102+
]
103+
},
104+
105+
validateWeight(): boolean {
106+
if (this.selectedStrategy === 'sampling_without_replacement') {
107+
return true
108+
} else {
109+
return this.workloadAllocation.reduce((acc, cur) => acc + cur, 0) === 100
110+
}
111+
}
112+
},
113+
114+
methods: {
115+
agree() {
116+
const workloads = this.workloadAllocation.map((weight, i) => ({
117+
weight,
118+
member_id: this.members[i].id
119+
}))
120+
console.log(workloads)
121+
this.$repositories.assignment.bulkAssign(this.projectId, {
122+
strategy_name: this.selectedStrategy,
123+
workloads
124+
})
125+
this.$emit('assigned')
126+
},
127+
cancel() {
128+
this.$emit('cancel')
129+
}
130+
}
131+
})
132+
</script>

frontend/pages/projects/_id/dataset/index.vue

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<action-menu
55
@upload="$router.push('dataset/import')"
66
@download="$router.push('dataset/export')"
7+
@assign="dialogAssignment = true"
78
/>
89
<v-btn
910
class="text-capitalize ms-2"
@@ -33,6 +34,9 @@
3334
<v-dialog v-model="dialogDeleteAll">
3435
<form-delete-bulk @cancel="dialogDeleteAll = false" @remove="removeAll" />
3536
</v-dialog>
37+
<v-dialog v-model="dialogAssignment">
38+
<form-assignment @assigned="assigned" @cancel="dialogAssignment = false" />
39+
</v-dialog>
3640
</v-card-title>
3741
<image-list
3842
v-if="project.isImageProject"
@@ -83,6 +87,7 @@ import { mapGetters } from 'vuex'
8387
import Vue from 'vue'
8488
import { NuxtAppOptions } from '@nuxt/types'
8589
import DocumentList from '@/components/example/DocumentList.vue'
90+
import FormAssignment from '~/components/example/FormAssignment.vue'
8691
import FormDelete from '@/components/example/FormDelete.vue'
8792
import FormDeleteBulk from '@/components/example/FormDeleteBulk.vue'
8893
import ActionMenu from '~/components/example/ActionMenu.vue'
@@ -98,6 +103,7 @@ export default Vue.extend({
98103
AudioList,
99104
DocumentList,
100105
ImageList,
106+
FormAssignment,
101107
FormDelete,
102108
FormDeleteBulk
103109
},
@@ -114,6 +120,7 @@ export default Vue.extend({
114120
return {
115121
dialogDelete: false,
116122
dialogDeleteAll: false,
123+
dialogAssignment: false,
117124
item: {} as ExampleListDTO,
118125
selected: [] as ExampleDTO[],
119126
members: [] as MemberItem[],
@@ -204,6 +211,11 @@ export default Vue.extend({
204211
async unassign(assignmentId: string) {
205212
await this.$repositories.assignment.unassign(this.projectId, assignmentId)
206213
this.item = await this.$services.example.list(this.projectId, this.$route.query)
214+
},
215+
216+
async assigned() {
217+
this.dialogAssignment = false
218+
this.item = await this.$services.example.list(this.projectId, this.$route.query)
207219
}
208220
}
209221
})

frontend/repositories/example/apiAssignmentRepository.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,9 @@ export class APIAssignmentRepository {
1515
const url = `/projects/${projectId}/assignments/${assignmentId}`
1616
await this.request.delete(url)
1717
}
18+
19+
async bulkAssign(projectId: string, workloadAllocation: Object): Promise<void> {
20+
const url = `/projects/${projectId}/assignments/bulk_assign`
21+
await this.request.post(url, workloadAllocation)
22+
}
1823
}

0 commit comments

Comments
 (0)