Skip to content

Commit 119532c

Browse files
authored
Add placeholder component when lacking edit access (#333)
* Add placeholder component when lacking edit access * Add comment explaining experiment refresh * Add check for "force"=True
1 parent 034a0af commit 119532c

File tree

5 files changed

+86
-32
lines changed

5 files changed

+86
-32
lines changed

client/src/components/ControlPanel.vue

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default {
2121
window: 256,
2222
level: 150,
2323
newExperimentNote: '',
24+
loadingLock: undefined,
2425
}),
2526
computed: {
2627
...mapState([
@@ -46,6 +47,11 @@ export default {
4647
experimentId() {
4748
return this.currentViewData.experimentId;
4849
},
50+
editRights() {
51+
return this.myCurrentProjectRoles.includes('tier_1_reviewer')
52+
|| this.myCurrentProjectRoles.includes('tier_2_reviewer')
53+
|| this.myCurrentProjectRoles.includes('superuser');
54+
},
4955
experimentIsEditable() {
5056
return this.lockOwner && this.lockOwner.id === this.user.id;
5157
},
@@ -129,14 +135,13 @@ export default {
129135
'setShowCrosshairs',
130136
'setStoreCrosshairs',
131137
]),
132-
async switchLock(newExp, oldExp = null) {
138+
async switchLock(newExp, oldExp = null, force = false) {
133139
if (!this.navigateToNextIfCurrentScanNull()) {
134-
if (this.myCurrentProjectRoles.includes('tier_1_reviewer')
135-
|| this.myCurrentProjectRoles.includes('tier_2_reviewer')
136-
|| this.myCurrentProjectRoles.includes('superuser')) {
140+
if (this.editRights) {
141+
this.loadingLock = true;
137142
if (oldExp) {
138143
try {
139-
await this.setLock({ experimentId: oldExp, lock: false });
144+
await this.setLock({ experimentId: oldExp, lock: false, force });
140145
} catch (err) {
141146
this.$snackbar({
142147
text: 'Failed to release edit access on Experiment.',
@@ -145,12 +150,13 @@ export default {
145150
}
146151
}
147152
try {
148-
await this.setLock({ experimentId: newExp, lock: true });
153+
await this.setLock({ experimentId: newExp, lock: true, force });
149154
} catch (err) {
150155
this.$snackbar({
151156
text: 'Failed to claim edit access on Experiment.',
152157
timeout: 6000,
153158
});
159+
this.loadingLock = false;
154160
}
155161
}
156162
}
@@ -547,9 +553,40 @@ export default {
547553
</v-col>
548554
<v-col cols="6">
549555
<DecisionButtons
550-
:experimentIsEditable="experimentIsEditable"
556+
v-if="experimentIsEditable"
551557
@handleKeyPress="handleKeyPress"
552558
/>
559+
<div
560+
v-else
561+
class="uneditable-notice"
562+
>
563+
<v-icon>mdi-lock</v-icon>
564+
You {{ editRights ?'have not claimed' :'do not have' }}
565+
edit access on this Experiment.
566+
<div
567+
v-if="lockOwner"
568+
class="my-3"
569+
style="text-align:center"
570+
>
571+
<UserAvatar
572+
:target-user="lockOwner"
573+
as-editor
574+
/>
575+
<br>
576+
{{ lockOwner.username }}
577+
<br>
578+
currently has edit access.
579+
</div>
580+
<v-btn
581+
v-if="editRights && (user.is_superuser || !lockOwner)"
582+
:loading="loadingLock"
583+
:disabled="loadingLock"
584+
@click="switchLock(experimentId, null, force=true)"
585+
color="primary"
586+
>
587+
{{ lockOwner ?"Steal edit access" :"Claim edit access" }}
588+
</v-btn>
589+
</div>
553590
</v-col>
554591
</v-row>
555592
</v-container>
@@ -585,4 +622,12 @@ export default {
585622
}
586623
}
587624
625+
.uneditable-notice {
626+
display: flex;
627+
flex-flow: column wrap;
628+
width: 100%;
629+
height: 100%;
630+
justify-content: center;
631+
align-content: center;
632+
}
588633
</style>

client/src/components/DecisionButtons.vue

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script>
22
import _ from 'lodash';
3-
import { mapGetters, mapState } from 'vuex';
3+
import { mapGetters, mapMutations, mapState } from 'vuex';
44
import djangoRest from '@/django';
55
import store from '@/store';
66
import EvaluationResults from '@/components/EvaluationResults.vue';
@@ -11,12 +11,6 @@ export default {
1111
components: {
1212
EvaluationResults,
1313
},
14-
props: {
15-
experimentIsEditable: {
16-
type: Boolean,
17-
default: false,
18-
},
19-
},
2014
data() {
2115
return {
2216
warnDecision: false,
@@ -108,6 +102,9 @@ export default {
108102
},
109103
},
110104
methods: {
105+
...mapMutations([
106+
'updateExperiment',
107+
]),
111108
convertValueToLabel(artifactName) {
112109
return artifactName
113110
.replace('susceptibility_metal', 'metal_susceptibility')
@@ -210,9 +207,16 @@ export default {
210207
this.newComment = '';
211208
} catch (err) {
212209
this.$snackbar({
213-
text: `Save failed: ${err.response.data.detail || 'Server error'}`,
210+
text: `Save failed: ${err || 'Server error'}`,
214211
timeout: 6000,
215212
});
213+
// If error is due a lock contention, it is likely because someone claimed the lock
214+
// after we got the experiment data
215+
// (else we would already know about the lock owner and not attempt to lock).
216+
// Thus, we need to update our experiment's info and check who the lock owner is
217+
if (err.toString().includes('lock')) {
218+
this.updateExperiment(await djangoRest.experiment(this.currentViewData.experimentId));
219+
}
216220
}
217221
} else {
218222
this.warnDecision = true;
@@ -224,7 +228,6 @@ export default {
224228

225229
<template>
226230
<v-container
227-
v-if="experimentIsEditable"
228231
fluid
229232
class="px-5"
230233
>

client/src/django.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ const djangoClient = {
144144
const { data } = await apiClient.post(`/experiments/${experimentId}/note`, { note });
145145
return data;
146146
},
147-
async lockExperiment(experimentId: string) {
148-
const { data } = await apiClient.post(`/experiments/${experimentId}/lock`);
147+
async lockExperiment(experimentId: string, force: boolean) {
148+
const { data } = await apiClient.post(`/experiments/${experimentId}/lock`, { force });
149149
return data;
150150
},
151151
async unlockExperiment(experimentId: string) {

client/src/store/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -833,11 +833,11 @@ const {
833833
// If necessary, queue loading scans of new experiment
834834
checkLoadExperiment(oldExperiment, newExperiment);
835835
},
836-
async setLock({ commit }, { experimentId, lock }) {
836+
async setLock({ commit }, { experimentId, lock, force }) {
837837
if (lock) {
838838
commit(
839839
'updateExperiment',
840-
await djangoRest.lockExperiment(experimentId),
840+
await djangoRest.lockExperiment(experimentId, force),
841841
);
842842
} else {
843843
commit(

miqa/core/rest/experiment.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -115,20 +115,26 @@ def lock(self, request, pk=None):
115115
experiment: Experiment = Experiment.objects.select_for_update().get(pk=pk)
116116
if experiment.project.archived:
117117
raise ArchivedProject()
118-
if experiment.lock_owner is not None and experiment.lock_owner != request.user:
118+
if (
119+
experiment.lock_owner is not None
120+
and experiment.lock_owner != request.user
121+
and not (
122+
request.user.is_superuser and 'force' in request.data and request.data['force']
123+
)
124+
):
119125
raise LockContention()
120-
if experiment.lock_owner is None or experiment.lock_owner == request.user:
121-
previously_locked_experiments = Experiment.objects.filter(lock_owner=request.user)
122-
for previously_locked_experiment in previously_locked_experiments:
123-
previously_locked_experiment.lock_owner = None
124-
previously_locked_experiment.save()
125-
experiment.lock_owner = request.user
126-
experiment.save(update_fields=['lock_owner'])
127126

128-
return Response(
129-
ExperimentSerializer(experiment).data,
130-
status=status.HTTP_200_OK,
131-
)
127+
previously_locked_experiments = Experiment.objects.filter(lock_owner=request.user)
128+
for previously_locked_experiment in previously_locked_experiments:
129+
previously_locked_experiment.lock_owner = None
130+
previously_locked_experiment.save(update_fields=['lock_owner'])
131+
experiment.lock_owner = request.user
132+
experiment.save(update_fields=['lock_owner'])
133+
134+
return Response(
135+
ExperimentSerializer(experiment).data,
136+
status=status.HTTP_200_OK,
137+
)
132138
return Response(status=status.HTTP_204_NO_CONTENT)
133139

134140
@swagger_auto_schema(

0 commit comments

Comments
 (0)