mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge pull request #5582 from Ocelot-Social-Community/list-all-groups
feat: 🍰 List All Groups
This commit is contained in:
commit
a6517ca0d8
@ -312,6 +312,7 @@ export default shield(
|
||||
currentUser: allow,
|
||||
Group: isAuthenticated,
|
||||
GroupMembers: isAllowedSeeingGroupMembers,
|
||||
GroupCount: isAuthenticated,
|
||||
Post: allow,
|
||||
profilePagePosts: allow,
|
||||
Comment: allow,
|
||||
|
||||
@ -14,7 +14,9 @@ import { createOrUpdateLocations } from './users/location'
|
||||
export default {
|
||||
Query: {
|
||||
Group: async (_object, params, context, _resolveInfo) => {
|
||||
const { isMember, id, slug } = params
|
||||
const { isMember, id, slug, first, offset } = params
|
||||
let pagination = ''
|
||||
if (first !== undefined && offset !== undefined) pagination = `SKIP ${offset} LIMIT ${first}`
|
||||
const matchParams = { id, slug }
|
||||
removeUndefinedNullValuesFromObject(matchParams)
|
||||
const session = context.driver.session()
|
||||
@ -27,6 +29,7 @@ export default {
|
||||
WITH group, membership
|
||||
WHERE (group.groupType IN ['public', 'closed']) OR (group.groupType = 'hidden' AND membership.role IN ['usual', 'admin', 'owner'])
|
||||
RETURN group {.*, myRole: membership.role}
|
||||
${pagination}
|
||||
`
|
||||
} else {
|
||||
if (isMember === false) {
|
||||
@ -36,6 +39,7 @@ export default {
|
||||
WITH group
|
||||
WHERE group.groupType IN ['public', 'closed']
|
||||
RETURN group {.*, myRole: NULL}
|
||||
${pagination}
|
||||
`
|
||||
} else {
|
||||
groupCypher = `
|
||||
@ -44,6 +48,7 @@ export default {
|
||||
WITH group, membership
|
||||
WHERE (group.groupType IN ['public', 'closed']) OR (group.groupType = 'hidden' AND membership.role IN ['usual', 'admin', 'owner'])
|
||||
RETURN group {.*, myRole: membership.role}
|
||||
${pagination}
|
||||
`
|
||||
}
|
||||
}
|
||||
@ -81,6 +86,39 @@ export default {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
GroupCount: async (_object, params, context, _resolveInfo) => {
|
||||
const { isMember } = params
|
||||
const {
|
||||
user: { id: userId },
|
||||
} = context
|
||||
const session = context.driver.session()
|
||||
const readTxResultPromise = session.readTransaction(async (txc) => {
|
||||
let cypher
|
||||
if (isMember) {
|
||||
cypher = `MATCH (user:User)-[membership:MEMBER_OF]->(group:Group)
|
||||
WHERE user.id = $userId
|
||||
AND membership.role IN ['usual', 'admin', 'owner']
|
||||
RETURN toString(count(group)) AS count`
|
||||
} else {
|
||||
cypher = `MATCH (group:Group)
|
||||
OPTIONAL MATCH (user:User)-[membership:MEMBER_OF]->(group)
|
||||
WHERE user.id = $userId
|
||||
WITH group, membership
|
||||
WHERE group.groupType IN ['public', 'closed']
|
||||
OR membership.role IN ['usual', 'admin', 'owner']
|
||||
RETURN toString(count(group)) AS count`
|
||||
}
|
||||
const transactionResponse = await txc.run(cypher, { userId })
|
||||
return transactionResponse.records.map((record) => record.get('count'))
|
||||
})
|
||||
try {
|
||||
return parseInt(await readTxResultPromise)
|
||||
} catch (error) {
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
CreateGroup: async (_parent, params, context, _resolveInfo) => {
|
||||
|
||||
@ -65,8 +65,8 @@ type Query {
|
||||
isMember: Boolean # if 'undefined' or 'null' then get all groups
|
||||
id: ID
|
||||
slug: String
|
||||
# first: Int # not implemented yet
|
||||
# offset: Int # not implemented yet
|
||||
first: Int
|
||||
offset: Int
|
||||
# orderBy: [_GroupOrdering] # not implemented yet
|
||||
# filter: _GroupFilter # not implemented yet
|
||||
): [Group]
|
||||
@ -79,6 +79,8 @@ type Query {
|
||||
# filter: _UserFilter # not implemented yet
|
||||
): [User]
|
||||
|
||||
GroupCount(isMember: Boolean): Int
|
||||
|
||||
# AvailableGroupTypes: [GroupType]!
|
||||
|
||||
# AvailableGroupActionRadii: [GroupActionRadius]!
|
||||
|
||||
@ -90,10 +90,10 @@ describe('AvatarMenu.vue', () => {
|
||||
expect(profileLink.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays a link to "My groups"', () => {
|
||||
it('displays a link to "Groups"', () => {
|
||||
const profileLink = wrapper
|
||||
.findAll('.ds-menu-item span')
|
||||
.at(wrapper.vm.routes.findIndex((route) => route.path === '/my-groups'))
|
||||
.at(wrapper.vm.routes.findIndex((route) => route.path === '/groups'))
|
||||
expect(profileLink.exists()).toBe(true)
|
||||
})
|
||||
|
||||
|
||||
@ -77,8 +77,8 @@ export default {
|
||||
icon: 'user',
|
||||
},
|
||||
{
|
||||
name: this.$t('header.avatarMenu.myGroups'),
|
||||
path: '/my-groups',
|
||||
name: this.$t('header.avatarMenu.Groups'),
|
||||
path: '/groups',
|
||||
icon: 'users',
|
||||
},
|
||||
{
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<nuxt-link to="/my-groups"><base-button icon="users" circle ghost /></nuxt-link>
|
||||
<nuxt-link to="/groups"><base-button icon="users" circle ghost /></nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -163,7 +163,7 @@
|
||||
|
||||
<!-- submit -->
|
||||
<ds-space margin-top="large">
|
||||
<nuxt-link to="/my-groups">
|
||||
<nuxt-link to="/groups">
|
||||
<ds-button>{{ $t('actions.cancel') }}</ds-button>
|
||||
</nuxt-link>
|
||||
<ds-button type="submit" icon="save" primary :disabled="checkFormError(errors)" fill>
|
||||
|
||||
@ -145,8 +145,8 @@ export const changeGroupMemberRoleMutation = () => {
|
||||
export const groupQuery = (i18n) => {
|
||||
const lang = i18n ? i18n.locale().toUpperCase() : 'EN'
|
||||
return gql`
|
||||
query ($isMember: Boolean, $id: ID, $slug: String) {
|
||||
Group(isMember: $isMember, id: $id, slug: $slug) {
|
||||
query ($isMember: Boolean, $id: ID, $slug: String, $first: Int, $offset: Int) {
|
||||
Group(isMember: $isMember, id: $id, slug: $slug, first: $first, offset: $offset) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
@ -190,3 +190,11 @@ export const groupMembersQuery = () => {
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
export const groupCountQuery = () => {
|
||||
return gql`
|
||||
query ($isMember: Boolean) {
|
||||
GroupCount(isMember: $isMember)
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
@ -407,6 +407,7 @@
|
||||
"addMemberToGroup": "Zur Gruppe hinzufügen",
|
||||
"addUser": "Benutzer hinzufügen",
|
||||
"addUserPlaceholder": "eindeutiger Benutzername > @slug-from-user",
|
||||
"allGroups": "Alle Gruppen",
|
||||
"categories": "Thema ::: Themen",
|
||||
"changeMemberRole": "Die Rolle wurde auf „{role}“ geändert!",
|
||||
"contentMenu": {
|
||||
@ -476,7 +477,7 @@
|
||||
},
|
||||
"header": {
|
||||
"avatarMenu": {
|
||||
"myGroups": "Mein Gruppen",
|
||||
"Groups": "Gruppen",
|
||||
"myProfile": "Mein Profil"
|
||||
}
|
||||
},
|
||||
|
||||
@ -407,6 +407,7 @@
|
||||
"addMemberToGroup": "Add to group",
|
||||
"addUser": "Add User",
|
||||
"addUserPlaceholder": "unique username > @slug-from-user",
|
||||
"allGroups": "All Groups",
|
||||
"categories": "Topic ::: Topics",
|
||||
"changeMemberRole": "The role has been changed to “{role}”!",
|
||||
"contentMenu": {
|
||||
@ -476,7 +477,7 @@
|
||||
},
|
||||
"header": {
|
||||
"avatarMenu": {
|
||||
"myGroups": "My groups",
|
||||
"Groups": "Groups",
|
||||
"myProfile": "My profile"
|
||||
}
|
||||
},
|
||||
|
||||
@ -58,7 +58,6 @@ export default {
|
||||
},
|
||||
})
|
||||
this.$toast.success(this.$t('group.groupCreated'))
|
||||
// this.$router.history.push('/my-groups')
|
||||
this.$router.history.push({
|
||||
name: 'group-id-slug',
|
||||
params: { id: responseId, slug: responseSlug },
|
||||
|
||||
35
webapp/pages/groups.spec.js
Normal file
35
webapp/pages/groups.spec.js
Normal file
@ -0,0 +1,35 @@
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import groups from './groups.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
config.stubs['nuxt-link'] = '<span class="nuxt-link"><slot /></span>'
|
||||
config.stubs['client-only'] = '<span class="client-only"><slot /></span>'
|
||||
|
||||
describe('groups', () => {
|
||||
let wrapper
|
||||
let mocks
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
const Wrapper = () => {
|
||||
return mount(groups, {
|
||||
mocks,
|
||||
localVue,
|
||||
})
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.is('div')).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
192
webapp/pages/groups.vue
Normal file
192
webapp/pages/groups.vue
Normal file
@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-space margin="small">
|
||||
<tab-navigation :tabs="tabOptions" :activeTab="tabActive" @switch-tab="handleTab" />
|
||||
</ds-space>
|
||||
<ds-space margin="large" />
|
||||
<ds-container>
|
||||
<!-- create group -->
|
||||
<ds-space centered>
|
||||
<nuxt-link :to="{ name: 'group-create' }">
|
||||
<base-button
|
||||
class="group-add-button"
|
||||
icon="plus"
|
||||
size="large"
|
||||
circle
|
||||
filled
|
||||
v-tooltip="{
|
||||
content: $t('group.createNewGroup.tooltip'),
|
||||
placement: 'left',
|
||||
}"
|
||||
/>
|
||||
</nuxt-link>
|
||||
</ds-space>
|
||||
<!-- group list -->
|
||||
<ds-space centered v-if="showPagination">
|
||||
<pagination-buttons
|
||||
:hasNext="hasNext"
|
||||
:showPageCounter="true"
|
||||
:hasPrevious="hasPrevious"
|
||||
:activePage="activePage"
|
||||
:activeResourceCount="activeTab.count"
|
||||
:key="'Top'"
|
||||
:pageSize="pageSize"
|
||||
@back="previousResults"
|
||||
@next="nextResults"
|
||||
/>
|
||||
</ds-space>
|
||||
<group-list :groups="myGroups" />
|
||||
<ds-space centered v-if="showPagination">
|
||||
<pagination-buttons
|
||||
:hasNext="hasNext"
|
||||
:showPageCounter="true"
|
||||
:hasPrevious="hasPrevious"
|
||||
:activePage="activePage"
|
||||
:activeResourceCount="activeTab.count"
|
||||
:key="'Bottom'"
|
||||
:pageSize="pageSize"
|
||||
@back="previousResults"
|
||||
@next="nextResults"
|
||||
/>
|
||||
</ds-space>
|
||||
</ds-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GroupList from '~/components/Group/GroupList'
|
||||
import { groupQuery, groupCountQuery } from '~/graphql/groups.js'
|
||||
import TabNavigation from '~/components/_new/generic/TabNavigation/TabNavigation'
|
||||
import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
|
||||
|
||||
const tabToFilterMapping = (tab) => {
|
||||
return {
|
||||
myGroups: { isMember: true },
|
||||
allGroups: {},
|
||||
}[tab]
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'Groups',
|
||||
components: {
|
||||
GroupList,
|
||||
TabNavigation,
|
||||
PaginationButtons,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
Group: [],
|
||||
groupFilter: { isMember: true },
|
||||
tabActive: 'myGroups',
|
||||
pageSize: 5,
|
||||
activePage: 0,
|
||||
myGroupsCount: 0,
|
||||
allGroupsCount: 0,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTab(tab) {
|
||||
if (this.tabActive !== tab) {
|
||||
this.tabActive = tab
|
||||
this.activePage = 0
|
||||
this.groupFilter = tabToFilterMapping(tab)
|
||||
this.$apollo.queries.Group.refetch()
|
||||
}
|
||||
},
|
||||
previousResults() {
|
||||
this.activePage--
|
||||
this.$apollo.queries.Group.refetch()
|
||||
},
|
||||
nextResults() {
|
||||
this.activePage++
|
||||
this.$apollo.queries.Group.refetch()
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
activeTab() {
|
||||
return this.tabOptions.find((tab) => tab.type === this.tabActive)
|
||||
},
|
||||
showPagination() {
|
||||
return this.activeTab.count > this.pageSize
|
||||
},
|
||||
hasNext() {
|
||||
return (this.activePage + 1) * this.pageSize < this.activeTab.count
|
||||
},
|
||||
hasPrevious() {
|
||||
return this.activePage > 0
|
||||
},
|
||||
pagination() {
|
||||
return {
|
||||
first: this.pageSize,
|
||||
offset: this.activePage * this.pageSize,
|
||||
}
|
||||
},
|
||||
myGroups() {
|
||||
return this.Group ? this.Group : []
|
||||
},
|
||||
tabOptions() {
|
||||
return [
|
||||
{
|
||||
type: 'myGroups',
|
||||
title: this.$t('group.myGroups'),
|
||||
count: this.myGroupsCount,
|
||||
disabled: this.myGroupsCount === 0,
|
||||
},
|
||||
{
|
||||
type: 'allGroups',
|
||||
title: this.$t('group.allGroups'),
|
||||
count: this.allGroupsCount,
|
||||
disabled: this.allGroupsCount === 0,
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
Group: {
|
||||
query() {
|
||||
return groupQuery(this.$i18n)
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
...this.groupFilter,
|
||||
...this.pagination,
|
||||
}
|
||||
},
|
||||
error(error) {
|
||||
this.Group = []
|
||||
this.$toast.error(error.message)
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
MyGroupsCount: {
|
||||
query() {
|
||||
return groupCountQuery()
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
isMember: true,
|
||||
}
|
||||
},
|
||||
update({ GroupCount }) {
|
||||
this.myGroupsCount = GroupCount
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
AllGroupsCount: {
|
||||
query() {
|
||||
return groupCountQuery()
|
||||
},
|
||||
update({ GroupCount }) {
|
||||
this.allGroupsCount = GroupCount
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.group-add-button {
|
||||
box-shadow: $box-shadow-x-large;
|
||||
}
|
||||
</style>
|
||||
@ -1,73 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<ds-space margin="small">
|
||||
<ds-heading tag="h1">{{ $t('group.myGroups') }}</ds-heading>
|
||||
</ds-space>
|
||||
<ds-space margin="large" />
|
||||
<ds-container>
|
||||
<!-- create group -->
|
||||
<ds-space centered>
|
||||
<nuxt-link :to="{ name: 'group-create' }">
|
||||
<base-button
|
||||
class="group-add-button"
|
||||
icon="plus"
|
||||
size="large"
|
||||
circle
|
||||
filled
|
||||
v-tooltip="{
|
||||
content: $t('group.createNewGroup.tooltip'),
|
||||
placement: 'left',
|
||||
}"
|
||||
/>
|
||||
</nuxt-link>
|
||||
</ds-space>
|
||||
<!-- group list -->
|
||||
<group-list :groups="myGroups" />
|
||||
</ds-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GroupList from '~/components/Group/GroupList'
|
||||
import { groupQuery } from '~/graphql/groups.js'
|
||||
|
||||
export default {
|
||||
name: 'MyGroups',
|
||||
components: {
|
||||
GroupList,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
Group: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
myGroups() {
|
||||
return this.Group ? this.Group : []
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
Group: {
|
||||
query() {
|
||||
return groupQuery(this.$i18n)
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
isMember: true,
|
||||
}
|
||||
},
|
||||
error(error) {
|
||||
this.Group = []
|
||||
this.$toast.error(error.message)
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.group-add-button {
|
||||
box-shadow: $box-shadow-x-large;
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user