From d41b99b9135625c0d94053b612f5292f77caf87c Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 6 Mar 2023 15:00:52 +0100 Subject: [PATCH 1/9] feat(backend): filter posts of my groups --- .../helpers/filterPostsOfMyGroups.js | 40 +++++++++++++++++++ backend/src/schema/resolvers/posts.js | 3 ++ backend/src/schema/types/type/Post.gql | 1 + 3 files changed, 44 insertions(+) create mode 100644 backend/src/schema/resolvers/helpers/filterPostsOfMyGroups.js diff --git a/backend/src/schema/resolvers/helpers/filterPostsOfMyGroups.js b/backend/src/schema/resolvers/helpers/filterPostsOfMyGroups.js new file mode 100644 index 000000000..a808a5582 --- /dev/null +++ b/backend/src/schema/resolvers/helpers/filterPostsOfMyGroups.js @@ -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 +} diff --git a/backend/src/schema/resolvers/posts.js b/backend/src/schema/resolvers/posts.js index 78515e641..d806f3803 100644 --- a/backend/src/schema/resolvers/posts.js +++ b/backend/src/schema/resolvers/posts.js @@ -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) => { @@ -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) diff --git a/backend/src/schema/types/type/Post.gql b/backend/src/schema/types/type/Post.gql index 9eac00b0b..6fc7a3215 100644 --- a/backend/src/schema/types/type/Post.gql +++ b/backend/src/schema/types/type/Post.gql @@ -82,6 +82,7 @@ input _PostFilter { emotions_single: _PostEMOTEDFilter emotions_every: _PostEMOTEDFilter group: _GroupFilter + postsInMyGroups: Boolean } enum _PostOrdering { From 11a7dc28816fbf803d28e39ad562b1a7b6497c5f Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 6 Mar 2023 15:28:43 +0100 Subject: [PATCH 2/9] test filter posts in my groups --- .../schema/resolvers/postsInGroups.spec.js | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/backend/src/schema/resolvers/postsInGroups.spec.js b/backend/src/schema/resolvers/postsInGroups.spec.js index 5bf5820f0..02c7311a7 100644 --- a/backend/src/schema/resolvers/postsInGroups.spec.js +++ b/backend/src/schema/resolvers/postsInGroups.spec.js @@ -60,7 +60,7 @@ beforeAll(async () => { }) afterAll(async () => { - await cleanDatabase() + // await cleanDatabase() driver.close() }) @@ -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, + }) + }) + }) + }) }) }) From 77ca09ed04170828fc66ad120070a17beb7d791b Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 6 Mar 2023 15:32:29 +0100 Subject: [PATCH 3/9] after all clean DB --- backend/src/schema/resolvers/postsInGroups.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/schema/resolvers/postsInGroups.spec.js b/backend/src/schema/resolvers/postsInGroups.spec.js index 02c7311a7..1eaf7a708 100644 --- a/backend/src/schema/resolvers/postsInGroups.spec.js +++ b/backend/src/schema/resolvers/postsInGroups.spec.js @@ -60,7 +60,7 @@ beforeAll(async () => { }) afterAll(async () => { - // await cleanDatabase() + await cleanDatabase() driver.close() }) From d2462c34f1eb2beffc15babae243ff52dc068028 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 6 Mar 2023 16:00:50 +0100 Subject: [PATCH 4/9] add filter by posts in my groups to store and test it --- webapp/store/posts.js | 16 +++++++++++++++ webapp/store/posts.spec.js | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/webapp/store/posts.js b/webapp/store/posts.js index 5d9891eff..e8602bb13 100644 --- a/webapp/store/posts.js +++ b/webapp/store/posts.js @@ -30,6 +30,19 @@ export const mutations = { } } }, + TOGGLE_FILTER_BY_MY_GROUPS(state) { + const filter = clone(state.filter) + const status = get(filter, 'postsInMyGroups') + if (status) { + delete filter.postsInMyGroups + state.filter = filter + } else { + state.filter = { + ...filter, + postsInMyGroups: true, + } + } + }, RESET_CATEGORIES(state) { const filter = clone(state.filter) delete filter.categories_some @@ -84,6 +97,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') || [] }, diff --git a/webapp/store/posts.spec.js b/webapp/store/posts.spec.js index ed728bdd8..665b147ae 100644 --- a/webapp/store/posts.spec.js +++ b/webapp/store/posts.spec.js @@ -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'] } } } @@ -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) => { From b63ade8da1e5392fe84117b9375b61f7c9c2c121 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 6 Mar 2023 16:05:15 +0100 Subject: [PATCH 5/9] add filter posts by my groups --- webapp/components/FilterMenu/FilterMenuSection.vue | 5 +++-- webapp/components/FilterMenu/FollowingFilter.vue | 11 +++++++++++ webapp/locales/de.json | 2 ++ webapp/locales/en.json | 2 ++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/webapp/components/FilterMenu/FilterMenuSection.vue b/webapp/components/FilterMenu/FilterMenuSection.vue index 921bafff0..39c819563 100644 --- a/webapp/components/FilterMenu/FilterMenuSection.vue +++ b/webapp/components/FilterMenu/FilterMenuSection.vue @@ -4,13 +4,14 @@
    +
+ + -->
    diff --git a/webapp/components/FilterMenu/FollowingFilter.vue b/webapp/components/FilterMenu/FollowingFilter.vue index 7c2c2a282..93f0c05b4 100644 --- a/webapp/components/FilterMenu/FollowingFilter.vue +++ b/webapp/components/FilterMenu/FollowingFilter.vue @@ -10,6 +10,15 @@ @click="toggleFilteredByFollowed(currentUser.id)" /> +
  • + +
  • @@ -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', }), }, } diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 9ea733068..2c7d84354 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -273,6 +273,7 @@ "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", @@ -380,6 +381,7 @@ "filter-by": "Filtern nach ...", "following": "Nutzer denen ich folge", "languages": "Sprachen", + "my-groups": "Meinen Gruppen", "order": { "newest": { "hint": "Sortiere die Neuesten nach vorn", diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 9e45396b8..87e7318c7 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -273,6 +273,7 @@ "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", @@ -380,6 +381,7 @@ "filter-by": "Filter by ...", "following": "Users I follow", "languages": "Languages", + "my-groups": "My groups", "order": { "newest": { "hint": "Sort posts by the newest first", From a784b0f86000e9ccae1832f0fbbe992ef8c4a9b5 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 6 Mar 2023 16:11:17 +0100 Subject: [PATCH 6/9] test filter by posts in my groups --- .../FilterMenu/FollowingFilter.spec.js | 17 +++++++++++++++++ .../components/FilterMenu/FollowingFilter.vue | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/webapp/components/FilterMenu/FollowingFilter.spec.js b/webapp/components/FilterMenu/FollowingFilter.spec.js index 4d4a827e5..0f51b305c 100644 --- a/webapp/components/FilterMenu/FollowingFilter.spec.js +++ b/webapp/components/FilterMenu/FollowingFilter.spec.js @@ -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 = { @@ -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', () => { @@ -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() + }) + }) }) }) diff --git a/webapp/components/FilterMenu/FollowingFilter.vue b/webapp/components/FilterMenu/FollowingFilter.vue index 93f0c05b4..9a488acd6 100644 --- a/webapp/components/FilterMenu/FollowingFilter.vue +++ b/webapp/components/FilterMenu/FollowingFilter.vue @@ -16,7 +16,7 @@ :label="$t('filter-menu.my-groups')" :filled="filteredByPostsInMyGroups" :title="$t('contribution.filterMyGroups')" - @click="toggleFilteredByMyGroups(currentUser.id)" + @click="toggleFilteredByMyGroups()" /> From 1575ee97652bb4b31d16f2cae508221a43394c60 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 6 Mar 2023 16:20:45 +0100 Subject: [PATCH 7/9] =?UTF-8?q?Ebertsche=20Kn=C3=B6pfe=20for=20filter=20by?= =?UTF-8?q?=20posts=20in=20my=20groups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/locales/de.json | 1 + webapp/locales/en.json | 1 + webapp/pages/index.vue | 21 ++++++++++++++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 2c7d84354..1db5a3dcd 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -270,6 +270,7 @@ "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" }, diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 87e7318c7..f7a7a54ad 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -270,6 +270,7 @@ "filterFollow": "Filter contributions from users I follow", "filterMasonryGrid": { "myFriends": "Users I follow", + "myGroups": "My groups", "myTopics": "My topics", "noFilter": "Filter posts" }, diff --git a/webapp/pages/index.vue b/webapp/pages/index.vue index d5f1ac3d0..ddadec9a8 100644 --- a/webapp/pages/index.vue +++ b/webapp/pages/index.vue @@ -30,7 +30,11 @@
    + + + {{ $t('contribution.filterMasonryGrid.myGroups') }} + + + +
    @@ -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', }), From 1fa883111057dc46ffb38adc7c2e386d364d6ba4 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 6 Mar 2023 16:25:45 +0100 Subject: [PATCH 8/9] undo changes to filter menu section --- webapp/components/FilterMenu/FilterMenuSection.vue | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/webapp/components/FilterMenu/FilterMenuSection.vue b/webapp/components/FilterMenu/FilterMenuSection.vue index 39c819563..921bafff0 100644 --- a/webapp/components/FilterMenu/FilterMenuSection.vue +++ b/webapp/components/FilterMenu/FilterMenuSection.vue @@ -4,14 +4,13 @@
      -
    + + -->
      From 32aa5892a67e4dedc74b4f6c733cb231941128af Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 6 Mar 2023 16:29:35 +0100 Subject: [PATCH 9/9] improve code --- webapp/store/posts.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/webapp/store/posts.js b/webapp/store/posts.js index e8602bb13..587867b0e 100644 --- a/webapp/store/posts.js +++ b/webapp/store/posts.js @@ -32,8 +32,7 @@ export const mutations = { }, TOGGLE_FILTER_BY_MY_GROUPS(state) { const filter = clone(state.filter) - const status = get(filter, 'postsInMyGroups') - if (status) { + if (get(filter, 'postsInMyGroups')) { delete filter.postsInMyGroups state.filter = filter } else {