feat(ui): grant and revoke stream permissions

This commit is contained in:
Matteo Cominetti
2020-10-14 22:41:53 +01:00
parent c3059fa574
commit 30bd4edf2e
8 changed files with 255 additions and 63 deletions
+18 -10
View File
@@ -1,5 +1,5 @@
<template>
<v-row>
<v-row align="center">
<v-col cols="1">
<v-avatar color="grey lighten-3" size="40">
<v-img v-if="user.avatar" :src="user.avatar" />
@@ -9,7 +9,7 @@
/>
</v-avatar>
</v-col>
<v-col cols="11">
<v-col cols="7">
<div class="subtitle-2">
<!-- <router-link :to="'streams/' + stream.id"> -->
{{ user.name }}
@@ -19,17 +19,25 @@
{{ user.company }}
</div>
</v-col>
<v-col cols="2" justify="center" class="caption">
<i>
{{ user.role.replace("stream:", "") }}
</i>
</v-col>
<v-col cols="2" justify="center">
<v-btn
v-if="!isUniqueStreamOwner"
icon
small
@click="userRemoveClick(user.id)"
>
<v-icon small color="error">mdi-account-remove-outline</v-icon>
</v-btn>
</v-col>
</v-row>
</template>
<script>
export default {
props: {
user: {
type: Object,
default: function () {
return {}
}
}
}
props: ["user", "userRemoveClick", "isUniqueStreamOwner"]
}
</script>
+28 -1
View File
@@ -66,6 +66,7 @@
<v-card-title class="subtitle-1">Collaborators</v-card-title>
<v-card-actions class="ml-2 mr-2">
<v-btn
v-if="isStreamOwner"
small
fab
color="primary"
@@ -78,6 +79,8 @@
<stream-share-dialog
ref="streamShareDialog"
:users="stream.collaborators"
:stream-id="stream.id"
:user-id="user.id"
></stream-share-dialog>
<v-avatar
@@ -115,9 +118,33 @@ export default {
id: this.$route.params.streamId
}
}
},
user: {
prefetch: true,
query: gql`
query {
user {
id
}
}
`
}
},
data: () => ({ user: {} }),
computed: {
isStreamOwner() {
return (
this.stream.collaborators.filter(
(x) => x.id === this.user.id && x.role === "stream:owner"
).length > 0
)
}
},
watch: {
user(val) {
//console.log(val)
}
},
data: () => ({}),
methods: {
shareStream() {
this.$refs.streamShareDialog.open()
@@ -1,5 +1,5 @@
<template>
<v-dialog v-model="show" width="600" @keydown.esc="cancel">
<v-dialog v-model="dialog" width="600" @keydown.esc="dialog = false">
<v-card class="pa-4">
<v-card-title class="subtitle-1">Manage collaborators</v-card-title>
@@ -8,19 +8,18 @@
<v-row>
<v-col cols="12" class="pb-0">
<v-autocomplete
v-model="select"
v-model="selectedUsers"
:loading="$apollo.loading"
:items="userSearch.items"
:items="items"
:search-input.sync="search"
multiple
chips
autofocus
hide-no-data
cache-items
label="Search"
placeholder="Add collaborators"
label="Users"
placeholder="Type to search..."
item-text="name"
item-value="id"
return-object
clearable
>
<template #selection="{ attr, on, item, selected }">
@@ -62,13 +61,61 @@
</v-autocomplete>
</v-col>
</v-row>
<v-row class="mb-5" align="center">
<v-col cols="12" class="pt-0 pb-0">
<v-card-actions>
<v-spacer></v-spacer>
<v-select
v-model="selectedRole"
:items="roles"
item-text="name"
return-object
label="Role"
class="mr-5"
dense
style="width: 40px"
>
<template #selection="{ item }">
<span class="caption">
{{ item.name.replace("stream:", "") }}
</span>
</template>
<template #item="{ item }">
<span class="caption">
{{ item.name.replace("stream:", "") }}
</span>
</template>
</v-select>
<v-btn
class="primary mb-3"
:disabled="
!selectedUsers ||
selectedUsers.length === 0 ||
!selectedRole
"
@click="grantStreamPermission"
>
Add collaborators
</v-btn>
</v-card-actions>
<div v-if="selectedRole" class="caption text-right">
{{ selectedRole.description }}
</div>
</v-col>
</v-row>
<v-row>
<v-col cols="12" class="pt-0 pb-0">
<list-item-user
v-for="(user, i) in users"
:key="i"
:user="user"
></list-item-user>
<div class="subtitle-1 pb-2">Collaborators</div>
<div v-for="(user, i) in stream.collaborators" :key="i">
<list-item-user
:user="user"
:user-remove-click="revokeStreamPermission"
:is-unique-stream-owner="isUniqueStreamOwner(user.id)"
></list-item-user>
<v-divider
v-if="i < stream.collaborators.length - 1"
></v-divider>
</div>
</v-col>
</v-row>
<v-row>
@@ -83,34 +130,42 @@
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" text @click.native="dialog = false">Save</v-btn>
<v-btn color="primary" text @click.native="dialog = false">Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import gql from "graphql-tag"
import serverQuery from "../../graphql/server.gql"
import streamCollaboratorsQuery from "../../graphql/streamCollaborators.gql"
import userSearchQuery from "../../graphql/userSearch.gql"
import ListItemUser from "../ListItemUser"
export default {
components: { ListItemUser },
props: {
users: {
type: Array,
default: function () {
return []
}
}
},
props: ["streamId", "userId"],
data: () => ({
dialog: true,
search: "",
query: "",
select: null,
userSearch: { items: [] }
selectedUsers: null,
selectedRole: null,
userSearch: { items: [] },
serverInfo: { roles: [] },
user: {}
}),
apollo: {
stream: {
prefetch: true,
query: streamCollaboratorsQuery,
variables() {
// Use vue reactive properties here
return {
id: this.streamId
}
}
},
userSearch: {
query: userSearchQuery,
variables() {
@@ -121,30 +176,31 @@ export default {
}
},
skip() {
return (
!this.search || this.search === this.select || this.search.length < 3
)
return !this.search || this.search.length < 3
}
},
serverInfo: {
prefetch: true,
query: serverQuery
}
},
computed: {
show: {
get() {
return this.dialog
},
set(value) {
this.dialog = value
if (value === false) {
this.cancel()
}
}
roles() {
return this.serverInfo.roles
.filter((x) => x.resourceTarget === "streams")
.reverse()
},
items() {
let items = []
this.userSearch.items.forEach((item) => {
if (this.stream.collaborators.map((x) => x.id).indexOf(item.id) === -1)
items.push(item)
})
return items
}
},
watch: {
users(val) {
console.log(val)
},
"userSearch.items"(val) {
selectedRole(val) {
console.log(val)
}
},
@@ -153,8 +209,87 @@ export default {
this.dialog = true
},
remove(item) {
const index = this.select.indexOf(item.id)
if (index >= 0) this.select.splice(index, 1)
console.log(item)
const index = this.selectedUsers.map((x) => x.id).indexOf(item.id)
if (index >= 0) this.selectedUsers.splice(index, 1)
},
// filter(item) {
// console.log("A")
// return (
// this.stream.collaborators.filter((x) => x.id === item.id).length === 0
// )
// },
isUniqueStreamOwner(id) {
return (
this.userId === id &&
this.stream.collaborators.filter((x) => x.role === "stream:owner")
.length === 1 &&
this.stream.collaborators.filter(
(x) => x.id === this.userId && x.role === "stream:owner"
).length === 1
)
},
grantStreamPermission() {
var promises = []
this.selectedUsers.forEach((user) => {
promises.push(
this.$apollo
.mutate({
mutation: gql`
mutation streamGrantPermission(
$permissionParams: StreamGrantPermissionInput!
) {
streamGrantPermission(permissionParams: $permissionParams)
}
`,
variables: {
permissionParams: {
streamId: this.streamId,
userId: user.id,
role: this.selectedRole.name
}
}
})
.then((data) => {
//
})
.catch((error) => {
// Error
console.error(error)
})
)
})
Promise.all(promises).then(() => {
this.$apollo.queries.stream.refetch()
this.selectedUsers = []
})
},
revokeStreamPermission(id) {
this.$apollo
.mutate({
mutation: gql`
mutation streamRevokePermission(
$permissionParams: StreamRevokePermissionInput!
) {
streamRevokePermission(permissionParams: $permissionParams)
}
`,
variables: {
permissionParams: {
streamId: this.streamId,
userId: id
}
}
})
.then((data) => {
this.$apollo.queries.stream.refetch()
})
.catch((error) => {
// Error
console.error(error)
})
}
}
}
+5
View File
@@ -5,5 +5,10 @@ query {
description
adminContact
canonicalUrl
roles {
name
description
resourceTarget
}
}
}
+1
View File
@@ -11,6 +11,7 @@ query Stream($id: String!) {
name
role
company
avatar
}
branches {
totalCount
@@ -0,0 +1,12 @@
query Stream($id: String!) {
stream(id: $id) {
id
collaborators {
id
name
role
company
avatar
}
}
}
+1
View File
@@ -23,6 +23,7 @@ query {
id
name
company
avatar
role
}
commits {
+13 -10
View File
@@ -7,16 +7,19 @@
<v-col v-if="user" cols="9">
<v-card rounded="lg" class="pa-5" elevation="0">
<v-card-title>Your Streams</v-card-title>
<v-btn
class="ml-3 mt-5 text-right"
color="primary"
elevation="0"
small
@click="newStream"
>
<v-icon small class="mr-1">mdi-plus-box-outline</v-icon>
new stream
</v-btn>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
class="ml-3 mt-5 text-right"
color="primary"
elevation="0"
small
@click="newStream"
>
<v-icon small class="mr-1">mdi-plus-box-outline</v-icon>
new stream
</v-btn>
</v-card-actions>
<stream-dialog ref="streamDialog"></stream-dialog>