From d6f8b0dc773e527ebdef1381fb4a41ca87147006 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 31 Aug 2022 14:28:25 +0200 Subject: [PATCH 01/11] change category to topic in german and english locales --- webapp/locales/de.json | 18 +++++++++--------- webapp/locales/en.json | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/webapp/locales/de.json b/webapp/locales/de.json index dee8d086a..b17e47d08 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -11,7 +11,7 @@ "admin": { "categories": { "categoryName": "Name", - "name": "Kategorien", + "name": "Themen", "postCount": "Beiträge" }, "dashboard": { @@ -98,7 +98,7 @@ } }, "common": { - "category": "Kategorie ::: Kategorien", + "category": "Thema ::: Themen", "comment": "Kommentar ::: Kommentare", "letsTalk": "Miteinander reden", "loading": "wird geladen", @@ -113,7 +113,7 @@ "takeAction": "Aktiv werden", "user": "Benutzer ::: Benutzer", "validations": { - "categories": "es müssen eine bis drei Kategorien ausgewählt werden", + "categories": "es müssen eine bis drei Themen ausgewählt werden", "email": "muss eine gültige E-Mail-Adresse sein", "url": "muss eine gültige URL sein" }, @@ -214,7 +214,7 @@ "amount-shouts": "{amount} recommendations", "amount-views": "{amount} views", "categories": { - "infoSelectedNoOfMaxCategories": "{chosen} von {max} Kategorien ausgewählt" + "infoSelectedNoOfMaxCategories": "{chosen} von {max} Themen ausgewählt" }, "category": { "name": { @@ -348,7 +348,7 @@ }, "filter-menu": { "all": "Alle", - "categories": "Themenkategorien", + "categories": "Themen", "emotions": "Emotionen", "filter-by": "Filtern nach ...", "following": "Benutzern, denen ich folge", @@ -470,7 +470,7 @@ "noDecision": "Keine Entscheidung!", "numberOfUsers": "{count} Nutzern", "previousDecision": "Vorherige Entscheidung:", - "reasonCategory": "Kategorie", + "reasonCategory": "Thema", "reasonDescription": "Beschreibung", "reportedOn": "Datum", "status": "Aktueller Status", @@ -587,8 +587,8 @@ }, "reason": { "category": { - "invalid": "Bitte wähle eine gültige Kategorie aus", - "label": "Wähle eine Kategorie:", + "invalid": "Bitte wähle ein gültiges Thema aus", + "label": "Wähle ein Thema:", "options": { "advert_products_services_commercial": "Bewerben von Produkten und Dienstleistungen mit kommerzieller Absicht.", "criminal_behavior_violation_german_law": "Strafbares Verhalten bzw. Verstoß gegen deutsches Recht.", @@ -599,7 +599,7 @@ "other": "Andere …", "pornographic_content_links": "Das Senden oder Verlinken eindeutig pornografischen Materials." }, - "placeholder": "Kategorie …" + "placeholder": "Thema …" }, "description": { "label": "Bitte erkläre: Warum möchtest Du dies melden?", diff --git a/webapp/locales/en.json b/webapp/locales/en.json index d6172c257..8587d9da5 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -11,7 +11,7 @@ "admin": { "categories": { "categoryName": "Name", - "name": "Categories", + "name": "Topics", "postCount": "Posts" }, "dashboard": { @@ -98,7 +98,7 @@ } }, "common": { - "category": "Category ::: Categories", + "category": "Topic ::: Topics", "comment": "Comment ::: Comments", "letsTalk": "Let`s Talk", "loading": "loading", @@ -113,7 +113,7 @@ "takeAction": "Take Action", "user": "User ::: Users", "validations": { - "categories": "at least one and at most three categories must be selected", + "categories": "at least one and at most three topics must be selected", "email": "must be a valid e-mail address", "url": "must be a valid URL" }, @@ -214,7 +214,7 @@ "amount-shouts": "{amount} recommendations", "amount-views": "{amount} views", "categories": { - "infoSelectedNoOfMaxCategories": "{chosen} of {max} categories selected" + "infoSelectedNoOfMaxCategories": "{chosen} of {max} topics selected" }, "category": { "name": { @@ -348,7 +348,7 @@ }, "filter-menu": { "all": "All", - "categories": "Categories of Content", + "categories": "Topics", "emotions": "Emotions", "filter-by": "Filter by ...", "following": "Users I follow", @@ -470,7 +470,7 @@ "noDecision": "No decision!", "numberOfUsers": "{count} users", "previousDecision": "Previous decision:", - "reasonCategory": "Category", + "reasonCategory": "Topic", "reasonDescription": "Description", "reportedOn": "Date", "status": "Current status", @@ -587,8 +587,8 @@ }, "reason": { "category": { - "invalid": "Please select a valid category", - "label": "Select a category:", + "invalid": "Please select a valid topic", + "label": "Select a topic:", "options": { "advert_products_services_commercial": "Advertising products and services with commercial intent.", "criminal_behavior_violation_german_law": "Criminal behavior or violation of German law.", @@ -599,7 +599,7 @@ "other": "Other …", "pornographic_content_links": "Posting or linking of clearly pornographic material." }, - "placeholder": "Category …" + "placeholder": "Topic …" }, "description": { "label": "Please explain: Why you like to report this?", From 87aa47d68345bf851cdc3b3e00ca1e434196ce6f Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 31 Aug 2022 14:32:46 +0200 Subject: [PATCH 02/11] add optional topica menu when categories are active --- .../components/FilterMenu/CategoriesMenu.vue | 58 +++++++++++++++++++ webapp/components/FilterMenu/FilterMenu.vue | 8 --- webapp/layouts/default.vue | 10 ++++ 3 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 webapp/components/FilterMenu/CategoriesMenu.vue diff --git a/webapp/components/FilterMenu/CategoriesMenu.vue b/webapp/components/FilterMenu/CategoriesMenu.vue new file mode 100644 index 000000000..f8a224003 --- /dev/null +++ b/webapp/components/FilterMenu/CategoriesMenu.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/webapp/components/FilterMenu/FilterMenu.vue b/webapp/components/FilterMenu/FilterMenu.vue index 84c7c1f67..9e211ccf9 100644 --- a/webapp/components/FilterMenu/FilterMenu.vue +++ b/webapp/components/FilterMenu/FilterMenu.vue @@ -14,7 +14,6 @@

{{ $t('filter-menu.filter-by') }}

-

{{ $t('filter-menu.order-by') }}

@@ -29,24 +28,17 @@ import Dropdown from '~/components/Dropdown' import { mapGetters } from 'vuex' import FollowingFilter from './FollowingFilter' import OrderByFilter from './OrderByFilter' -import CategoriesFilter from './CategoriesFilter' export default { components: { Dropdown, FollowingFilter, - CategoriesFilter, OrderByFilter, }, props: { placement: { type: String }, offset: { type: [String, Number] }, }, - data() { - return { - categoriesActive: this.$env.CATEGORIES_ACTIVE, - } - }, computed: { ...mapGetters({ filterActive: 'posts/isActive', diff --git a/webapp/layouts/default.vue b/webapp/layouts/default.vue index 0dfd752bf..97b070d11 100644 --- a/webapp/layouts/default.vue +++ b/webapp/layouts/default.vue @@ -15,6 +15,13 @@ > + + + Date: Mon, 5 Sep 2022 17:41:21 +0200 Subject: [PATCH 03/11] add saveCategoruSettings mutation to schema --- backend/src/middleware/permissionsMiddleware.js | 1 + backend/src/schema/types/type/User.gql | 2 ++ 2 files changed, 3 insertions(+) diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index b5ce4fe07..71a44f225 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -178,6 +178,7 @@ export default shield( GenerateInviteCode: isAuthenticated, switchUserRole: isAdmin, markTeaserAsViewed: allow, + saveCategorySettings: isAuthenticated, }, User: { email: or(isMyOwn, isAdmin), diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index 871e73ad8..b84606ff7 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -220,4 +220,6 @@ type Mutation { unblockUser(id: ID!): User switchUserRole(role: UserRole!, id: ID!): User + + saveCategorySettings(activeCategories: [String]): Boolean } From 572377acfde8e6a07f3fb32b598b4cf9b15b6969 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 5 Sep 2022 20:06:10 +0200 Subject: [PATCH 04/11] add active categories to user --- backend/src/schema/types/type/User.gql | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index b84606ff7..d2ac79037 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -114,6 +114,14 @@ type User { badgesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)") emotions: [EMOTED] + + activeCategories: [Category] @cypher( + statement: """ + MATCH (category:Category) + WHERE NOT ((this)-[:NOT_INTERESTED_IN]->(category)) + RETURN category + """ + ) } From fc6d544ea7399c711ba7ce5c6134cf0de94714ab Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 5 Sep 2022 20:07:14 +0200 Subject: [PATCH 05/11] add resolver for save active categories --- backend/src/schema/resolvers/users.js | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js index 5dc78c5e1..4ab721b06 100644 --- a/backend/src/schema/resolvers/users.js +++ b/backend/src/schema/resolvers/users.js @@ -269,6 +269,46 @@ export default { session.close() } }, + saveCategorySettings: async (object, args, context, resolveInfo) => { + const { activeCategories } = args + const { + user: { id }, + } = context + + const session = context.driver.session() + await session.writeTransaction((transaction) => { + return transaction.run( + ` + MATCH (user:User { id: $id })-[previousCategories:NOT_INTERESTED_IN]->(category:Category) + DELETE previousCategories + RETURN user, category + `, + { id }, + ) + }) + + const writeTxResultPromise = session.writeTransaction(async (transaction) => { + const saveCategorySettingsResponse = await transaction.run( + ` + MATCH (category:Category) WHERE NOT category.id IN $activeCategories + MATCH (user:User { id: $id }) + MERGE (user)-[r:NOT_INTERESTED_IN]->(category) + RETURN user, r, category + `, + { id, activeCategories }, + ) + const [user] = await saveCategorySettingsResponse.records.map((record) => + record.get('user'), + ) + return user + }) + try { + await writeTxResultPromise + return true + } finally { + session.close() + } + }, }, User: { email: async (parent, params, context, resolveInfo) => { From 4e5d4cb8734a8f5c581e78cd7b4717d82fc27a8d Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 5 Sep 2022 20:51:48 +0200 Subject: [PATCH 06/11] basic tests for save category settings --- backend/src/schema/resolvers/users.js | 1 - backend/src/schema/resolvers/users.spec.js | 102 +++++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js index 4ab721b06..23a39a2a1 100644 --- a/backend/src/schema/resolvers/users.js +++ b/backend/src/schema/resolvers/users.js @@ -286,7 +286,6 @@ export default { { id }, ) }) - const writeTxResultPromise = session.writeTransaction(async (transaction) => { const saveCategorySettingsResponse = await transaction.run( ` diff --git a/backend/src/schema/resolvers/users.spec.js b/backend/src/schema/resolvers/users.spec.js index 920ef52ea..e655bdf28 100644 --- a/backend/src/schema/resolvers/users.spec.js +++ b/backend/src/schema/resolvers/users.spec.js @@ -3,6 +3,7 @@ import { gql } from '../../helpers/jest' import { getNeode, getDriver } from '../../db/neo4j' import createServer from '../../server' import { createTestClient } from 'apollo-server-testing' +import { categories } from '../../constants/categories' const categoryIds = ['cat9'] let user @@ -56,6 +57,12 @@ const switchUserRoleMutation = gql` } ` +const saveCategorySettings = gql` + mutation ($activeCategories: [String]) { + saveCategorySettings(activeCategories: $activeCategories) + } +` + beforeAll(async () => { await cleanDatabase() @@ -544,3 +551,98 @@ describe('switch user role', () => { }) }) }) + +describe('save category settings', () => { + beforeAll(async () => { + await Promise.all( + categories.map(({ icon, name }, index) => { + Factory.build('category', { + id: `cat${index + 1}`, + slug: name, + name, + icon, + }) + }), + ) + }) + + beforeEach(async () => { + user = await Factory.build('user', { + id: 'user', + role: 'user', + }) + variables = { + activeCategories: ['cat1', 'cat3', 'cat5'], + } + }) + + describe('not authenticated', () => { + beforeEach(async () => { + authenticatedUser = undefined + }) + + it('throws an error', async () => { + await expect(mutate({ mutation: saveCategorySettings, variables })).resolves.toEqual( + expect.objectContaining({ + errors: [ + expect.objectContaining({ + message: 'Not Authorized!', + }), + ], + }), + ) + }) + }) + + describe('authenticated', () => { + beforeEach(async () => { + authenticatedUser = await user.toJson() + }) + + describe('no categories saved', () => { + it('returns true for active categories mutation', async () => { + await expect(mutate({ mutation: saveCategorySettings, variables })).resolves.toEqual( + expect.objectContaining({ + data: { saveCategorySettings: true }, + }), + ) + }) + + describe('query for user', () => { + beforeEach(async () => { + await mutate({ mutation: saveCategorySettings, variables }) + }) + + it.skip('returns the active categories when user is queried', async () => { + const userQuery = gql` + query ($id: ID) { + User(id: $id) { + activeCategories { + id + } + } + } + ` + + await expect( + query({ query: userQuery, variables: { id: authenticatedUser.id } }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + User: [ + { + activeCategories: expect.arrayContaining([ + { id: 'cat1' }, + { id: 'cat3' }, + { id: 'cat5' }, + ]), + }, + ], + }, + }), + ) + }) + }) + }) + }) +}) From 8a688ca78be0663527336f9b2502a3d796749308 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 6 Sep 2022 15:14:31 +0200 Subject: [PATCH 07/11] improve tests --- backend/src/schema/resolvers/users.spec.js | 72 ++++++++++++++++++---- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/backend/src/schema/resolvers/users.spec.js b/backend/src/schema/resolvers/users.spec.js index e655bdf28..7116d2201 100644 --- a/backend/src/schema/resolvers/users.spec.js +++ b/backend/src/schema/resolvers/users.spec.js @@ -553,7 +553,7 @@ describe('switch user role', () => { }) describe('save category settings', () => { - beforeAll(async () => { + beforeEach(async () => { await Promise.all( categories.map(({ icon, name }, index) => { Factory.build('category', { @@ -599,6 +599,16 @@ describe('save category settings', () => { authenticatedUser = await user.toJson() }) + const userQuery = gql` + query ($id: ID) { + User(id: $id) { + activeCategories { + id + } + } + } + ` + describe('no categories saved', () => { it('returns true for active categories mutation', async () => { await expect(mutate({ mutation: saveCategorySettings, variables })).resolves.toEqual( @@ -613,17 +623,7 @@ describe('save category settings', () => { await mutate({ mutation: saveCategorySettings, variables }) }) - it.skip('returns the active categories when user is queried', async () => { - const userQuery = gql` - query ($id: ID) { - User(id: $id) { - activeCategories { - id - } - } - } - ` - + it('returns the active categories when user is queried', async () => { await expect( query({ query: userQuery, variables: { id: authenticatedUser.id } }), ).resolves.toEqual( @@ -644,5 +644,53 @@ describe('save category settings', () => { }) }) }) + + describe('categories already saved', () => { + beforeEach(async () => { + variables = { + activeCategories: ['cat1', 'cat3', 'cat5'], + } + await mutate({ mutation: saveCategorySettings, variables }) + variables = { + activeCategories: ['cat10', 'cat11', 'cat12', 'cat8', 'cat9'], + } + }) + + it('returns true', async () => { + await expect(mutate({ mutation: saveCategorySettings, variables })).resolves.toEqual( + expect.objectContaining({ + data: { saveCategorySettings: true }, + }), + ) + }) + + describe('query for user', () => { + beforeEach(async () => { + await mutate({ mutation: saveCategorySettings, variables }) + }) + + it('returns the new active categories when user is queried', async () => { + await expect( + query({ query: userQuery, variables: { id: authenticatedUser.id } }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + User: [ + { + activeCategories: expect.arrayContaining([ + { id: 'cat10' }, + { id: 'cat11' }, + { id: 'cat12' }, + { id: 'cat8' }, + { id: 'cat9' }, + ]), + }, + ], + }, + }), + ) + }) + }) + }) }) }) From f5d8014f6c8c414debbc378d6dea89fba250137d Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 7 Sep 2022 11:06:43 +0200 Subject: [PATCH 08/11] fix: Category Filter Menu Client Only --- webapp/components/FilterMenu/CategoriesMenu.vue | 6 +++--- webapp/layouts/default.vue | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/webapp/components/FilterMenu/CategoriesMenu.vue b/webapp/components/FilterMenu/CategoriesMenu.vue index f8a224003..37bd19c84 100644 --- a/webapp/components/FilterMenu/CategoriesMenu.vue +++ b/webapp/components/FilterMenu/CategoriesMenu.vue @@ -1,5 +1,5 @@