Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(backend): filter posts in my groups #6091

Merged
merged 11 commits into from
Mar 7, 2023
40 changes: 40 additions & 0 deletions backend/src/schema/resolvers/helpers/filterPostsOfMyGroups.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { mergeWith, isArray } from 'lodash'

const getMyGroupIds = async (context) => {
const { user } = context
if (!(user && user.id)) return []
const session = context.driver.session()

const readTxResultPromise = await session.readTransaction(async (transaction) => {
const cypher = `
MATCH (group:Group)<-[membership:MEMBER_OF]-(:User { id: $userId })
WHERE membership.role IN ['usual', 'admin', 'owner']
RETURN collect(group.id) AS myGroupIds`
const getMyGroupIdsResponse = await transaction.run(cypher, { userId: user.id })
return getMyGroupIdsResponse.records.map((record) => record.get('myGroupIds'))
})
try {
const [myGroupIds] = readTxResultPromise
return myGroupIds
} finally {
session.close()
}
}

export const filterPostsOfMyGroups = async (params, context) => {
if (!(params.filter && params.filter.postsInMyGroups)) return params
delete params.filter.postsInMyGroups
const myGroupIds = await getMyGroupIds(context)
params.filter = mergeWith(
params.filter,
{
group: { id_in: myGroupIds },
},
(objValue, srcValue) => {
if (isArray(objValue)) {
return objValue.concat(srcValue)
}
},
)
return params
}
3 changes: 3 additions & 0 deletions backend/src/schema/resolvers/posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { mergeImage, deleteImage } from './images/images'
import Resolver from './helpers/Resolver'
import { filterForMutedUsers } from './helpers/filterForMutedUsers'
import { filterInvisiblePosts } from './helpers/filterInvisiblePosts'
import { filterPostsOfMyGroups } from './helpers/filterPostsOfMyGroups'
import CONFIG from '../../config'

const maintainPinnedPosts = (params) => {
Expand All @@ -21,12 +22,14 @@ const maintainPinnedPosts = (params) => {
export default {
Query: {
Post: async (object, params, context, resolveInfo) => {
params = await filterPostsOfMyGroups(params, context)
params = await filterInvisiblePosts(params, context)
params = await filterForMutedUsers(params, context)
params = await maintainPinnedPosts(params)
return neo4jgraphql(object, params, context, resolveInfo)
},
profilePagePosts: async (object, params, context, resolveInfo) => {
params = await filterPostsOfMyGroups(params, context)
params = await filterInvisiblePosts(params, context)
params = await filterForMutedUsers(params, context)
return neo4jgraphql(object, params, context, resolveInfo)
Expand Down
54 changes: 54 additions & 0 deletions backend/src/schema/resolvers/postsInGroups.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1678,5 +1678,59 @@ describe('Posts in Groups', () => {
})
})
})

describe('filter posts in my groups', () => {
describe('without any posts in groups', () => {
beforeAll(async () => {
authenticatedUser = await anyUser.toJson()
})

it('finds no posts', async () => {
const result = await query({
query: filterPosts(),
variables: { filter: { postsInMyGroups: true } },
})
expect(result.data.Post).toHaveLength(0)
expect(result).toMatchObject({
data: {
Post: [],
},
errors: undefined,
})
})
})

describe('with posts in groups', () => {
beforeAll(async () => {
// member of hidden-group and closed-group
authenticatedUser = await allGroupsUser.toJson()
})

it('finds two posts', async () => {
const result = await query({
query: filterPosts(),
variables: { filter: { postsInMyGroups: true } },
})
expect(result.data.Post).toHaveLength(2)
expect(result).toMatchObject({
data: {
Post: expect.arrayContaining([
{
id: 'post-to-closed-group',
title: 'A post to a closed group',
content: 'I am posting into a closed group as a member of the group',
},
{
id: 'post-to-hidden-group',
title: 'A post to a hidden group',
content: 'I am posting into a hidden group as a member of the group',
},
]),
},
errors: undefined,
})
})
})
})
})
})
1 change: 1 addition & 0 deletions backend/src/schema/types/type/Post.gql
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ input _PostFilter {
emotions_single: _PostEMOTEDFilter
emotions_every: _PostEMOTEDFilter
group: _GroupFilter
postsInMyGroups: Boolean
}

enum _PostOrdering {
Expand Down
17 changes: 17 additions & 0 deletions webapp/components/FilterMenu/FollowingFilter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ let wrapper
describe('FollowingFilter', () => {
const mutations = {
'posts/TOGGLE_FILTER_BY_FOLLOWED': jest.fn(),
'posts/TOGGLE_FILTER_BY_MY_GROUPS': jest.fn(),
}
const getters = {
'auth/user': () => {
return { id: 'u34' }
},
'posts/filteredByUsersFollowed': jest.fn(),
'posts/filteredByPostsInMyGroups': jest.fn(),
}

const mocks = {
Expand All @@ -34,12 +36,18 @@ describe('FollowingFilter', () => {
describe('mount', () => {
it('sets "filter-by-followed" button attribute `filled`', () => {
getters['posts/filteredByUsersFollowed'] = jest.fn(() => true)
getters['posts/filteredByPostsInMyGroups'] = jest.fn(() => true)
const wrapper = Wrapper()
expect(
wrapper
.find('.following-filter .filter-list .follower-item .base-button')
.classes('--filled'),
).toBe(true)
expect(
wrapper
.find('.following-filter .filter-list .posts-in-my-groups-item .base-button')
.classes('--filled'),
).toBe(true)
})

describe('click "filter-by-followed" button', () => {
Expand All @@ -48,5 +56,14 @@ describe('FollowingFilter', () => {
expect(mutations['posts/TOGGLE_FILTER_BY_FOLLOWED']).toHaveBeenCalledWith({}, 'u34')
})
})

describe('click "filter-by-my-groups" button', () => {
it('calls TOGGLE_FILTER_BY_MY_GROUPS', () => {
wrapper
.find('.following-filter .filter-list .posts-in-my-groups-item .base-button')
.trigger('click')
expect(mutations['posts/TOGGLE_FILTER_BY_MY_GROUPS']).toHaveBeenCalled()
})
})
})
})
11 changes: 11 additions & 0 deletions webapp/components/FilterMenu/FollowingFilter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@
@click="toggleFilteredByFollowed(currentUser.id)"
/>
</li>
<li class="item posts-in-my-groups-item">
<labeled-button
icon="users"
:label="$t('filter-menu.my-groups')"
:filled="filteredByPostsInMyGroups"
:title="$t('contribution.filterMyGroups')"
@click="toggleFilteredByMyGroups()"
/>
</li>
</template>
</filter-menu-section>
</template>
Expand All @@ -28,12 +37,14 @@ export default {
computed: {
...mapGetters({
filteredByUsersFollowed: 'posts/filteredByUsersFollowed',
filteredByPostsInMyGroups: 'posts/filteredByPostsInMyGroups',
currentUser: 'auth/user',
}),
},
methods: {
...mapMutations({
toggleFilteredByFollowed: 'posts/TOGGLE_FILTER_BY_FOLLOWED',
toggleFilteredByMyGroups: 'posts/TOGGLE_FILTER_BY_MY_GROUPS',
}),
},
}
Expand Down
3 changes: 3 additions & 0 deletions webapp/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,11 @@
"filterFollow": "Beiträge von Nutzern filtern, denen ich folge",
"filterMasonryGrid": {
"myFriends": "Nutzer denen ich folge",
"myGroups": "Meine Gruppen",
"myTopics": "Meine Themen",
"noFilter": "Beiträge filtern"
},
"filterMyGroups": "Beiträge in meinen Gruppen",
"inappropriatePicture": "Dieses Bild kann für einige Menschen unangemessen sein.",
"languageSelectLabel": "Sprache Deines Beitrags",
"languageSelectText": "Sprache wählen",
Expand Down Expand Up @@ -380,6 +382,7 @@
"filter-by": "Filtern nach ...",
"following": "Nutzer denen ich folge",
"languages": "Sprachen",
"my-groups": "Meinen Gruppen",
"order": {
"newest": {
"hint": "Sortiere die Neuesten nach vorn",
Expand Down
3 changes: 3 additions & 0 deletions webapp/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,11 @@
"filterFollow": "Filter contributions from users I follow",
"filterMasonryGrid": {
"myFriends": "Users I follow",
"myGroups": "My groups",
"myTopics": "My topics",
"noFilter": "Filter posts"
},
"filterMyGroups": "Contributions in my groups",
"inappropriatePicture": "This image may be inappropriate for some people.",
"languageSelectLabel": "Language of your contribution",
"languageSelectText": "Select Language",
Expand Down Expand Up @@ -380,6 +382,7 @@
"filter-by": "Filter by ...",
"following": "Users I follow",
"languages": "Languages",
"my-groups": "My groups",
"order": {
"newest": {
"hint": "Sort posts by the newest first",
Expand Down
21 changes: 20 additions & 1 deletion webapp/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@
<div class="filterButtonMenu" :class="{ 'hide-filter': hideByScroll }">
<base-button
class="my-filter-button"
v-if="!postsFilter['categories_some'] && !postsFilter['author']"
v-if="
!postsFilter['categories_some'] &&
!postsFilter['author'] &&
!postsFilter['postsInMyGroups']
"
right
@click="showFilter = !showFilter"
filled
Expand Down Expand Up @@ -66,6 +70,20 @@
/>
</span>

<span v-if="postsFilter['postsInMyGroups']">
<base-button class="my-filter-button" right @click="showFilter = !showFilter" filled>
{{ $t('contribution.filterMasonryGrid.myGroups') }}
</base-button>
<base-button
class="filter-remove"
@click="resetByGroups"
icon="close"
:title="$t('filter-menu.deleteFilter')"
style="margin-left: -8px"
filled
/>
</span>

<div id="my-filter" v-if="showFilter">
<div @mouseleave="showFilter = false">
<filter-menu-component @showFilterMenu="showFilterMenu" />
Expand Down Expand Up @@ -203,6 +221,7 @@ export default {
methods: {
...mapMutations({
resetByFollowed: 'posts/TOGGLE_FILTER_BY_FOLLOWED',
resetByGroups: 'posts/TOGGLE_FILTER_BY_MY_GROUPS',
resetCategories: 'posts/RESET_CATEGORIES',
toggleCategory: 'posts/TOGGLE_CATEGORY',
}),
Expand Down
15 changes: 15 additions & 0 deletions webapp/store/posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ export const mutations = {
}
}
},
TOGGLE_FILTER_BY_MY_GROUPS(state) {
const filter = clone(state.filter)
if (get(filter, 'postsInMyGroups')) {
delete filter.postsInMyGroups
state.filter = filter
} else {
state.filter = {
...filter,
postsInMyGroups: true,
}
}
},
RESET_CATEGORIES(state) {
const filter = clone(state.filter)
delete filter.categories_some
Expand Down Expand Up @@ -84,6 +96,9 @@ export const getters = {
filteredByUsersFollowed(state) {
return !!get(state.filter, 'author.followedBy_some.id')
},
filteredByPostsInMyGroups(state) {
return !!get(state.filter, 'postsInMyGroups')
},
filteredByEmotions(state) {
return get(state.filter, 'emotions_some.emotion_in') || []
},
Expand Down
41 changes: 41 additions & 0 deletions webapp/store/posts.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ describe('getters', () => {
})
})

describe('filteredByPostsInMyGroups', () => {
it('returns true if filter is set', () => {
state = { filter: { postsInMyGroups: true } }
expect(getters.filteredByPostsInMyGroups(state)).toBe(true)
})

it('returns false if filter is not set', () => {
state = { filter: { categories_some: { id_in: [23] } } }
expect(getters.filteredByUsersFollowed(state)).toBe(false)
})
})

describe('filteredByEmotions', () => {
it('returns an emotions array if filter is set', () => {
state = { filter: { emotions_some: { emotion_in: ['sad'] } } }
Expand Down Expand Up @@ -230,6 +242,35 @@ describe('mutations', () => {
})
})

describe('TOGGLE_FILTER_BY_MY_GROUPS', () => {
beforeEach(() => {
testMutation = () => {
mutations.TOGGLE_FILTER_BY_MY_GROUPS(state)
return getters.filter(state)
}
})

describe('given empty filter', () => {
beforeEach(() => {
state = { filter: {} }
})

it('sets postsInMyGroups filter to true', () => {
expect(testMutation()).toEqual({ postsInMyGroups: true })
})
})

describe('already filtered', () => {
beforeEach(() => {
state = { filter: { postsInMyGroups: true } }
})

it('removes postsInMyGroups filter', () => {
expect(testMutation()).toEqual({})
})
})
})

describe('TOGGLE_ORDER', () => {
beforeEach(() => {
testMutation = (key) => {
Expand Down