Skip to content

Commit 271c86f

Browse files
committed
Made group selection required when creating shared tasks
1 parent 5977044 commit 271c86f

File tree

7 files changed

+113
-12
lines changed

7 files changed

+113
-12
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { render, screen } from '@testing-library/svelte';
3+
4+
import TaskGroupSelector from '../../src/lib/components/v2/tasks/TaskGroupSelector.svelte';
5+
6+
describe('TaskGroupSelector', () => {
7+
it('Default group is selected', async () => {
8+
render(TaskGroupSelector, {
9+
props: {
10+
id: 'test',
11+
groupIdsNames: [
12+
[2, 'group2'],
13+
[1, 'All']
14+
],
15+
defaultGroupName: 'All'
16+
}
17+
});
18+
expect(screen.getByRole('combobox')).toHaveValue('1');
19+
});
20+
21+
it('Validate required group', async () => {
22+
const { component } = render(TaskGroupSelector, {
23+
props: {
24+
id: 'test',
25+
groupIdsNames: [],
26+
defaultGroupName: null
27+
}
28+
});
29+
30+
component.validate();
31+
32+
expect(await screen.findByText('Shared tasks must be associated with a group')).toBeVisible();
33+
});
34+
35+
it('The only present group is selected', async () => {
36+
render(TaskGroupSelector, {
37+
props: {
38+
id: 'test',
39+
groupIdsNames: [[1, 'group1']],
40+
defaultGroupName: null
41+
}
42+
});
43+
expect(screen.getByRole('combobox')).toHaveValue('1');
44+
});
45+
});

src/lib/components/v2/tasks/AddSingleTask.svelte

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
let taskType = $state('non_parallel');
3939
let privateTask = $state(false);
4040
let selectedGroup = $state(null);
41+
/** @type {TaskGroupSelector|undefined} */
42+
let taskGroupSelector = $state();
4143
4244
/** @type {TypesEditor|undefined} */
4345
let typesEditor = $state();
@@ -94,7 +96,7 @@
9496
9597
const typesValid = typesEditor?.validate();
9698
97-
if (Object.keys($validationErrors).length > 0 || !typesValid) {
99+
if (Object.keys($validationErrors).length > 0 || !typesValid || !taskGroupSelector?.validate()) {
98100
return;
99101
}
100102
@@ -652,6 +654,7 @@
652654
{defaultGroupName}
653655
bind:privateTask
654656
bind:selectedGroup
657+
bind:this={taskGroupSelector}
655658
/>
656659
657660
<div class="row">

src/lib/components/v2/tasks/CustomEnvTask.svelte

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
let manifestData = null;
2525
let privateTask = $state(false);
2626
let selectedGroup = $state(null);
27+
/** @type {TaskGroupSelector|undefined} */
28+
let taskGroupSelector = $state();
2729
let successMessage = $state('');
2830
2931
const formErrorHandler = new FormErrorHandler('errorAlert-customEnvTask', [
@@ -103,7 +105,7 @@
103105
headers.append('Content-Type', 'application/json');
104106
105107
let url = `/api/v2/task/collect/custom?private=${privateTask}`;
106-
if (!privateTask) {
108+
if (!privateTask && selectedGroup) {
107109
url += `&user_group_id=${selectedGroup}`;
108110
}
109111
@@ -146,9 +148,11 @@
146148
<StandardDismissableAlert message={successMessage} />
147149

148150
<form
149-
onsubmit={(e) => {
151+
onsubmit={async (e) => {
150152
e.preventDefault();
151-
handleCollect();
153+
if (taskGroupSelector?.validate()) {
154+
await handleCollect();
155+
}
152156
}}
153157
class="mb-5"
154158
>
@@ -284,6 +288,7 @@
284288
{defaultGroupName}
285289
bind:privateTask
286290
bind:selectedGroup
291+
bind:this={taskGroupSelector}
287292
/>
288293

289294
<button type="submit" class="btn btn-primary" disabled={collecting}>

src/lib/components/v2/tasks/PixiTask.svelte

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
let version = $state('');
2121
let privateTask = $state(false);
2222
let selectedGroup = $state(null);
23+
/** @type {TaskGroupSelector|undefined} */
24+
let taskGroupSelector = $state();
2325
2426
let taskCollectionInProgress = $state(false);
2527
@@ -52,7 +54,7 @@
5254
}
5355
5456
let url = `/api/v2/task/collect/pixi?private=${privateTask}`;
55-
if (!privateTask) {
57+
if (!privateTask && selectedGroup) {
5658
url += `&user_group_id=${selectedGroup}`;
5759
}
5860
@@ -81,7 +83,9 @@
8183
<form
8284
onsubmit={(e) => {
8385
e.preventDefault();
84-
handlePixiCollection();
86+
if (taskGroupSelector?.validate()) {
87+
handlePixiCollection();
88+
}
8589
}}
8690
>
8791
<div class="row">
@@ -134,6 +138,7 @@
134138
{defaultGroupName}
135139
bind:privateTask
136140
bind:selectedGroup
141+
bind:this={taskGroupSelector}
137142
/>
138143
</div>
139144
</div>

src/lib/components/v2/tasks/TaskCollection.svelte

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
let pinnedPackageVersions = $state([]);
2323
let privateTask = $state(false);
2424
let selectedGroup = $state(null);
25+
/** @type {TaskGroupSelector|undefined} */
26+
let taskGroupSelector = $state();
2527
2628
/** @type {FileList|null} */
2729
let wheelFiles = $state(null);
@@ -44,6 +46,7 @@
4446
python_version = '';
4547
package_extras = '';
4648
pinnedPackageVersions = [];
49+
taskGroupSelector?.clear();
4750
}
4851
4952
let taskCollectionInProgress = $state(false);
@@ -162,7 +165,9 @@
162165
<form
163166
onsubmit={(e) => {
164167
e.preventDefault();
165-
handleTaskCollection();
168+
if (taskGroupSelector?.validate()) {
169+
handleTaskCollection();
170+
}
166171
}}
167172
>
168173
<div class="row">
@@ -364,6 +369,7 @@
364369
{defaultGroupName}
365370
bind:privateTask
366371
bind:selectedGroup
372+
bind:this={taskGroupSelector}
367373
/>
368374
369375
<div id="taskCollectionError" class="mt-3"></div>

src/lib/components/v2/tasks/TaskGroupEditModal.svelte

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
let privateTask = $state(false);
2424
/** @type {number|undefined} */
2525
let selectedGroup = $state();
26+
/** @type {TaskGroupSelector|undefined} */
27+
let taskGroupSelector = $state();
2628
2729
let saving = $state(false);
2830
@@ -42,6 +44,9 @@
4244
}
4345
4446
async function handleUpdate() {
47+
if (!taskGroupSelector?.validate()) {
48+
return;
49+
}
4550
modal?.confirmAndHide(
4651
async () => {
4752
saving = true;
@@ -94,6 +99,7 @@
9499
{defaultGroupName}
95100
bind:privateTask
96101
bind:selectedGroup
102+
bind:this={taskGroupSelector}
97103
wrapperClass="mb-1"
98104
/>
99105
{/key}

src/lib/components/v2/tasks/TaskGroupSelector.svelte

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,35 @@
2121
wrapperClass = 'mt-3 mb-3'
2222
} = $props();
2323
24+
let validationError = $state('');
25+
2426
onMount(() => {
25-
selectAllGroup();
27+
selectDefaultGroup();
2628
});
2729
28-
function selectAllGroup() {
30+
function selectDefaultGroup() {
2931
if (selectedGroup === null) {
3032
const groupAll = groupIdsNames.find((i) => i[1] === defaultGroupName);
3133
if (groupAll) {
3234
selectedGroup = groupAll[0];
35+
} else if (groupIdsNames.length === 1) {
36+
selectedGroup = groupIdsNames[0][0];
3337
}
3438
}
3539
}
40+
41+
export function validate() {
42+
validationError = '';
43+
if (!privateTask && selectedGroup === null) {
44+
validationError = 'Shared tasks must be associated with a group';
45+
return false;
46+
}
47+
return true;
48+
}
49+
50+
export function clear() {
51+
validationError = '';
52+
}
3653
</script>
3754

3855
<div class={wrapperClass}>
@@ -46,7 +63,10 @@
4663
id="taskSelectorShared-{id}"
4764
value={false}
4865
bind:group={privateTask}
49-
onchange={selectAllGroup}
66+
onchange={() => {
67+
validationError = '';
68+
selectDefaultGroup();
69+
}}
5070
/>
5171
<label class="form-check-label" for="taskSelectorShared-{id}">Shared task</label>
5272
</div>
@@ -57,16 +77,22 @@
5777
name="privateTaskSelector-{id}"
5878
id="taskSelectorPrivate-{id}"
5979
value={true}
80+
onchange={() => (validationError = '')}
6081
bind:group={privateTask}
6182
/>
6283
<label class="form-check-label" for="taskSelectorPrivate-{id}">Private task</label>
6384
</div>
6485
</div>
65-
{#if !privateTask && groupIdsNames.length > 0}
86+
{#if !privateTask}
6687
<div class="col-12">
6788
<div class="input-group">
6889
<label class="input-group-text" for="task-group-selector">Group</label>
69-
<select class="form-select" id="task-group-selector" bind:value={selectedGroup}>
90+
<select
91+
class="form-select"
92+
id="task-group-selector"
93+
bind:value={selectedGroup}
94+
class:is-invalid={validationError}
95+
>
7096
<option value={null}>Select...</option>
7197
{#if groupIdsNames}
7298
{#each groupIdsNames as [groupId, groupName] (groupId)}
@@ -78,4 +104,9 @@
78104
</div>
79105
{/if}
80106
</div>
107+
{#if validationError}
108+
<div class="row text-danger mt-2">
109+
<div class="col">{validationError}</div>
110+
</div>
111+
{/if}
81112
</div>

0 commit comments

Comments
 (0)