Skip to content
This repository was archived by the owner on Jul 14, 2026. It is now read-only.

Commit d7680be

Browse files
committed
Added expiration date to collaborators
Added acceptance tests
1 parent e8421f0 commit d7680be

13 files changed

Lines changed: 413 additions & 40 deletions

File tree

apps/files/src/components/Collaborators/Collaborator.vue

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@
6565
(me)
6666
</translate>
6767
</div>
68-
<span class="oc-text"><span class="files-collaborators-collaborator-role">{{ originalRole.label }}</span><template v-if="collaborator.expires"> | <translate :translate-params="{expires: formDateFromNow(collaborator.expires)}">Expires: %{expires}</translate></template></span>
6968
<span class="uk-text-meta files-collaborators-collaborator-share-type" v-text="$_ocCollaborators_collaboratorType(collaborator.shareType)" />
69+
<span class="oc-text"><span class="files-collaborators-collaborator-role">{{ originalRole.label }}</span><template v-if="collaborator.expires"> | <translate class="files-collaborators-collaborator-expires" :translate-params="{expires: formDateFromNow(collaborator.expires)}">Expires %{expires}</translate></template></span>
7070
</div>
7171
</oc-table-cell>
7272
<oc-table-cell shrink>
@@ -94,12 +94,14 @@
9494
import { mapGetters } from 'vuex'
9595
import { shareTypes } from '../../helpers/shareTypes'
9696
import { basename, dirname } from 'path'
97-
import Mixins from '../../mixins/collaborators'
97+
import CollaboratorsMixins from '../../mixins/collaborators'
98+
import Mixins from '../../mixins'
9899
99100
export default {
100101
name: 'Collaborator',
101102
mixins: [
102-
Mixins
103+
Mixins,
104+
CollaboratorsMixins
103105
],
104106
props: {
105107
collaborator: {

apps/files/src/components/Collaborators/CollaboratorsEditOptions.vue

Lines changed: 163 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,54 @@
1414
:collaboratorsPermissions="collaboratorsPermissions"
1515
@permissionChecked="checkAdditionalPermissions"
1616
/>
17-
<oc-grid v-if="$_ocCollaborators_expirationSupported" gutter="small">
18-
<div class="uk-width-1-1">
19-
<label class="oc-label" for="files-collaborators-new-collaborator-expiration">
20-
<translate>Expiration date</translate>
21-
<translate class="uk-text-meta uk-remove-margin">(optional)</translate>
22-
</label>
23-
<oc-text-input type="date" class="uk-width-1-1 oc-button-role" id="files-collaborators-new-collaborator-expiration" :value="expirationDate">04 - 07 - 2019</oc-text-input>
17+
<div v-if="expirationSupported">
18+
<label for="files-collaborators-collaborator-expiration-input">
19+
<translate>Expiration date:</translate>
20+
<translate v-if="expirationDateEnforced" tag="em">(required)</translate>
21+
</label>
22+
<div class="uk-position-relative">
23+
<oc-datepicker
24+
id="files-collaborators-collaborator-expiration-input"
25+
:key="`collaborator-datepicker-${enteredExpirationDate}`"
26+
:date="enteredExpirationDate"
27+
:maxDatetime="maxExpirationDate"
28+
:minDatetime="minExpirationDate"
29+
:placeholder="expirationDatePlaceholder"
30+
@input="setExpirationDate"
31+
/>
32+
<div
33+
v-if="canResetExpirationDate"
34+
id="files-collaborators-collaborator-expiration-delete"
35+
class="uk-position-small uk-position-center-right oc-cursor-pointer"
36+
:uk-tooltip="expirationDateRemoveTooltip"
37+
@click="resetExpirationDate"
38+
uk-close
39+
/>
2440
</div>
25-
</oc-grid>
41+
</div>
2642
</oc-grid>
2743
</template>
2844

2945
<script>
46+
import { mapGetters } from 'vuex'
47+
import moment from 'moment'
3048
import collaboratorsMixins from '../../mixins/collaborators'
3149
3250
const RolesSelect = () => import('../Roles/RolesSelect.vue')
3351
const AdditionalPermissions = () => import('./AdditionalPermissions.vue')
3452
3553
export default {
3654
name: 'CollaboratorsEditOptions',
55+
3756
components: {
3857
RolesSelect,
3958
AdditionalPermissions
4059
},
60+
4161
mixins: [
4262
collaboratorsMixins
4363
],
64+
4465
props: {
4566
existingRole: {
4667
type: Object,
@@ -51,19 +72,35 @@ export default {
5172
required: false
5273
},
5374
expirationDate: {
54-
type: String,
75+
type: Date,
5576
required: false
77+
},
78+
existingCollaboratorType: {
79+
type: [Object, String],
80+
required: false,
81+
validator: function (value) {
82+
return ['user', 'group'].indexOf(value) > -1 || value === null
83+
}
5684
}
5785
},
86+
5887
data () {
5988
return {
6089
selectedRole: null,
61-
additionalPermissions: null
90+
additionalPermissions: null,
91+
enteredExpirationDate: null
6292
}
6393
},
94+
6495
computed: {
65-
$_ocCollaborators_expirationSupported () {
66-
return false
96+
...mapGetters(['capabilities']),
97+
98+
editingUser () {
99+
return this.existingCollaboratorType === 'user'
100+
},
101+
102+
editingGroup () {
103+
return this.existingCollaboratorType === 'group'
67104
},
68105
69106
$_ocCollaborators_hasAdditionalPermissions () {
@@ -98,8 +135,104 @@ export default {
98135
}
99136
100137
return this.selectedRole
138+
},
139+
140+
expirationSupported () {
141+
return this.userExpirationDate && this.groupExpirationDate
142+
},
143+
144+
defaultExpirationDateSet () {
145+
if (this.editingUser) {
146+
return this.userExpirationDate.enabled
147+
}
148+
149+
if (this.editingGroup) {
150+
return this.groupExpirationDate.enabled
151+
}
152+
153+
return this.userExpirationDate.enabled || this.groupExpirationDate.enabled
154+
},
155+
156+
userExpirationDate () {
157+
return this.capabilities.files_sharing.user.expire_date
158+
},
159+
160+
groupExpirationDate () {
161+
return this.capabilities.files_sharing.group.expire_date
162+
},
163+
164+
defaultExpirationDate () {
165+
if (!this.defaultExpirationDateSet) {
166+
return null
167+
}
168+
169+
const userMaxExpirationDays = parseInt(this.userExpirationDate.days, 10)
170+
const groupMaxExpirationDays = parseInt(this.groupExpirationDate.days, 10)
171+
172+
if (this.editingUser) {
173+
return moment().add(userMaxExpirationDays, 'days').endOf('day').toISOString()
174+
}
175+
176+
if (this.editingGroup) {
177+
return moment().add(groupMaxExpirationDays, 'days').endOf('day').toISOString()
178+
}
179+
180+
// Since we are not separating process for adding users and groups as collaborators
181+
// we are using the one which is smaller as enforced date
182+
let days = 0
183+
184+
if (userMaxExpirationDays && groupMaxExpirationDays) {
185+
days = Math.min(userMaxExpirationDays, groupMaxExpirationDays)
186+
} else {
187+
days = userMaxExpirationDays || groupMaxExpirationDays
188+
}
189+
190+
return moment().add(days, 'days').endOf('day').toISOString()
191+
},
192+
193+
expirationDateEnforced () {
194+
if (this.editingUser) {
195+
return this.userExpirationDate.enforced
196+
}
197+
198+
if (this.editingGroup) {
199+
return this.groupExpirationDate.enforced
200+
}
201+
202+
return this.userExpirationDate.enforced || this.groupExpirationDate.enforced
203+
},
204+
205+
maxExpirationDate () {
206+
if (!this.expirationDateEnforced) {
207+
return null
208+
}
209+
210+
return this.defaultExpirationDate
211+
},
212+
213+
minExpirationDate () {
214+
return moment().add(1, 'days').endOf('day').toISOString()
215+
},
216+
217+
expirationDatePlaceholder () {
218+
return this.$gettext('Expiration date')
219+
},
220+
221+
expirationDateRemoveTooltip () {
222+
return this.$gettext('Remove expiration date')
223+
},
224+
225+
canResetExpirationDate () {
226+
return !this.expirationDateEnforced && this.enteredExpirationDate
101227
}
102228
},
229+
230+
mounted () {
231+
// FIXME: Datepicker is not displaying correct timezone so for now we add it manually
232+
// this.enteredExpirationDate = this.expirationDate ? moment(this.expirationDate).toISOString(true) : this.defaultExpirationDate
233+
this.enteredExpirationDate = this.expirationDate ? moment(this.expirationDate).add(moment().utcOffset(), 'm').toISOString() : this.defaultExpirationDate
234+
},
235+
103236
methods: {
104237
selectRole (role) {
105238
this.selectedRole = role
@@ -111,8 +244,25 @@ export default {
111244
this.publishChange()
112245
},
113246
247+
setExpirationDate (date) {
248+
this.enteredExpirationDate = date
249+
this.publishChange()
250+
},
251+
252+
resetExpirationDate () {
253+
this.enteredExpirationDate = null
254+
this.publishChange()
255+
256+
// Emit reset event to register a change
257+
this.$emit('expirationDateResetted')
258+
},
259+
114260
publishChange () {
115-
this.$emit('optionChange', { role: this.selectedRole, permissions: this.additionalPermissions, expirationDate: this.expirationDate })
261+
this.$emit('optionChange', {
262+
role: this.selectedRole,
263+
permissions: this.additionalPermissions,
264+
expirationDate: this.enteredExpirationDate
265+
})
116266
}
117267
}
118268
}

apps/files/src/components/Collaborators/EditCollaborator.vue

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
<collaborators-edit-options
66
:existingRole="$_originalRole"
77
:collaboratorsPermissions="$_originalPermissions"
8+
:expirationDate="originalExpirationDate"
9+
:existingCollaboratorType="collaboratorType"
810
@optionChange="collaboratorOptionChanged"
11+
@expirationDateResetted="registerExpirationDateReset"
912
class="uk-margin-bottom"
1013
/>
1114
<hr class="divider" />
@@ -31,7 +34,9 @@
3134
<script>
3235
import filterObject from 'filter-obj'
3336
import { mapGetters, mapActions } from 'vuex'
37+
import moment from 'moment'
3438
import { roleToBitmask, bitmaskToRole } from '../../helpers/collaborators'
39+
import { shareTypes } from '../../helpers/shareTypes'
3540
import Collaborator from './Collaborator.vue'
3641
import CollaboratorsEditOptions from './CollaboratorsEditOptions.vue'
3742
import Mixins from '../../mixins/collaborators'
@@ -55,13 +60,34 @@ export default {
5560
return {
5661
selectedRole: null,
5762
additionalPermissions: null,
58-
saving: false
63+
saving: false,
64+
expirationDate: null,
65+
// Since expiration date can ba reset to null we can't depend on it when checking for changes
66+
expirationDateResetted: false
5967
}
6068
},
6169
computed: {
6270
...mapGetters('Files', ['highlightedFile']),
6371
...mapGetters(['user']),
6472
73+
collaboratorType () {
74+
const collaboratorShareType = this.collaborator.shareType
75+
76+
if (
77+
collaboratorShareType === shareTypes.user ||
78+
collaboratorShareType === shareTypes.guest ||
79+
collaboratorShareType === shareTypes.remote
80+
) {
81+
return 'user'
82+
}
83+
84+
if (collaboratorShareType === shareTypes.group) {
85+
return 'group'
86+
}
87+
88+
return null
89+
},
90+
6591
$_originalPermissions () {
6692
const permissions = this.collaborator.customPermissions
6793
return filterObject(permissions, (key, value) => value)
@@ -82,8 +108,26 @@ export default {
82108
// if the role has changed, always return true. The user doesn't need to understand if two bitmasks of different roles are the same!
83109
return true
84110
}
111+
112+
// FIXME: Datepicker is not displaying correct timezone so for now we add it manually
113+
const originalExpirationDate = moment(this.originalExpirationDate).add(moment().utcOffset(), 'm').toISOString()
114+
115+
if ((this.expirationDate || this.expirationDateResetted) && this.expirationDate !== originalExpirationDate) {
116+
return true
117+
}
118+
85119
const originalBitmask = roleToBitmask(this.$_originalRole, Object.keys(this.$_originalPermissions))
86120
return originalBitmask !== this.$_permissionsBitmask
121+
},
122+
123+
originalExpirationDate () {
124+
const expirationDate = this.collaborator.expires
125+
126+
if (expirationDate) {
127+
return expirationDate
128+
}
129+
130+
return null
87131
}
88132
},
89133
methods: {
@@ -100,7 +144,8 @@ export default {
100144
share: this.collaborator,
101145
// Map bitmask to role to get the correct role in case the advanced role was mapped to existing role
102146
role: bitmaskToRole(bitmask, this.highlightedFile.type === 'folder'),
103-
permissions: bitmask
147+
permissions: bitmask,
148+
expirationDate: this.expirationDate
104149
})
105150
.then(() => this.$_ocCollaborators_cancelChanges())
106151
.catch(() => {
@@ -111,12 +156,18 @@ export default {
111156
$_ocCollaborators_cancelChanges () {
112157
this.selectedRole = null
113158
this.additionalPermissions = null
159+
this.expirationDate = this.originalExpirationDate
160+
this.expirationDateResetted = false
114161
this.saving = false
115162
this.close()
116163
},
117164
118165
close () {
119166
this.$emit('close')
167+
},
168+
169+
registerExpirationDateReset () {
170+
this.expirationDateResetted = true
120171
}
121172
}
122173
}

apps/files/src/components/Collaborators/NewCollaborator.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ export default {
9595
selectedCollaborators: [],
9696
selectedRole: null,
9797
additionalPermissions: null,
98-
saving: false
98+
saving: false,
99+
expirationDate: null
99100
}
100101
},
101102
computed: {
@@ -209,7 +210,8 @@ export default {
209210
$gettext: this.$gettext,
210211
shareWith: collaborator.value.shareWith,
211212
shareType: collaborator.value.shareType,
212-
permissions: roleToBitmask(this.selectedRole, this.additionalPermissions)
213+
permissions: roleToBitmask(this.selectedRole, this.additionalPermissions),
214+
expirationDate: this.expirationDate
213215
})))
214216
})
215217

0 commit comments

Comments
 (0)