diff --git a/backend/src/constants/categories.js b/backend/src/constants/categories.js index 16df63a48..0d61a041a 100644 --- a/backend/src/constants/categories.js +++ b/backend/src/constants/categories.js @@ -4,7 +4,7 @@ export const CATEGORIES_MAX = 3 export const categories = [ { - icon: 'users', + icon: 'networking', name: 'networking', description: 'Kooperation, Aktionsbündnisse, Solidarität, Hilfe', }, @@ -14,12 +14,12 @@ export const categories = [ description: 'Bauen, Lebensgemeinschaften, Tiny Houses, Gemüsegarten', }, { - icon: 'lightbulb', + icon: 'energy', name: 'energy', description: 'Öl, Gas, Kohle, Wind, Wasserkraft, Biogas, Atomenergie, ...', }, { - icon: 'smile', + icon: 'psyche', name: 'psyche', description: 'Seele, Gefühle, Glück', }, @@ -34,7 +34,7 @@ export const categories = [ description: 'Menschenrechte, Gesetze, Verordnungen', }, { - icon: 'money', + icon: 'finance', name: 'finance', description: 'Geld, Finanzsystem, Alternativwährungen, ...', }, @@ -44,7 +44,7 @@ export const categories = [ description: 'Familie, Pädagogik, Schule, Prägung', }, { - icon: 'suitcase', + icon: 'mobility', name: 'mobility', description: 'Reise, Verkehr, Elektromobilität', }, @@ -54,48 +54,48 @@ export const categories = [ description: 'Handel, Konsum, Marketing, Lebensmittel, Lieferketten, ...', }, { - icon: 'angellist', + icon: 'peace', name: 'peace', description: 'Krieg, Militär, soziale Verteidigung, Waffen, Cyberattacken', }, { - icon: 'university', + icon: 'politics', name: 'politics', description: 'Demokratie, Mitbestimmung, Wahlen, Korruption, Parteien', }, { - icon: 'tree', + icon: 'nature', name: 'nature', description: 'Tiere, Pflanzen, Landwirtschaft, Ökologie, Artenvielfalt', }, { - icon: 'graduation-cap', + icon: 'science', name: 'science', description: 'Bildung, Hochschule, Publikationen, ...', }, { - icon: 'medkit', + icon: 'health', name: 'health', description: 'Medizin, Ernährung, WHO, Impfungen, Schadstoffe, ...', }, { - icon: 'desktop', + icon: 'media', name: 'it-and-media', description: 'Nachrichten, Manipulation, Datenschutz, Überwachung, Datenkraken, AI, Software, Apps', }, { - icon: 'heart-o', + icon: 'spirituality', name: 'spirituality', description: 'Religion, Werte, Ethik', }, { - icon: 'music', + icon: 'culture', name: 'culture', description: 'Kunst, Theater, Musik, Fotografie, Film', }, { - icon: 'ellipsis-h', + icon: 'miscellaneous', name: 'miscellaneous', description: '', }, diff --git a/backend/src/schema/resolvers/user_management.js b/backend/src/schema/resolvers/user_management.js index beb2cddb3..d88eafdae 100644 --- a/backend/src/schema/resolvers/user_management.js +++ b/backend/src/schema/resolvers/user_management.js @@ -20,16 +20,22 @@ export default { const result = await transaction.run( ` MATCH (user:User {id: $id}) - WITH user, [(user)<-[:OWNED_BY]-(medium:SocialMedia) | properties(medium) ] as media - RETURN user {.*, socialMedia: media } as user + OPTIONAL MATCH (category:Category) WHERE NOT ((user)-[:NOT_INTERESTED_IN]->(category)) + OPTIONAL MATCH (cats:Category) + WITH user, [(user)<-[:OWNED_BY]-(medium:SocialMedia) | properties(medium) ] AS media, category, toString(COUNT(cats)) AS categoryCount + RETURN user {.*, socialMedia: media, activeCategories: collect(category.id) } AS user, categoryCount `, { id: user.id }, ) - log(result) - return result.records.map((record) => record.get('user')) + const [categoryCount] = result.records.map((record) => record.get('categoryCount')) + const [currentUser] = result.records.map((record) => record.get('user')) + // frontend expects empty array when all categories are selected + if (currentUser.activeCategories.length === parseInt(categoryCount)) + currentUser.activeCategories = [] + return currentUser }) try { - const [currentUser] = await currentUserTransactionPromise + const currentUser = await currentUserTransactionPromise return currentUser } finally { session.close() diff --git a/backend/src/schema/resolvers/user_management.spec.js b/backend/src/schema/resolvers/user_management.spec.js index 20e9ef9d3..50e6e84bf 100644 --- a/backend/src/schema/resolvers/user_management.spec.js +++ b/backend/src/schema/resolvers/user_management.spec.js @@ -7,6 +7,7 @@ import { createTestClient } from 'apollo-server-testing' import createServer, { context } from '../../server' import encode from '../../jwt/encode' import { getNeode } from '../../db/neo4j' +import { categories } from '../../constants/categories' const neode = getNeode() let query, mutate, variables, req, user @@ -119,6 +120,7 @@ describe('currentUser', () => { } email role + activeCategories } } ` @@ -173,6 +175,52 @@ describe('currentUser', () => { } await respondsWith(expected) }) + + describe('with categories in DB', () => { + beforeEach(async () => { + await Promise.all( + categories.map(async ({ icon, name }, index) => { + await Factory.build('category', { + id: `cat${index + 1}`, + slug: name, + name, + icon, + }) + }), + ) + }) + + it('returns empty array for all categories', async () => { + await respondsWith({ + data: { + currentUser: expect.objectContaining({ activeCategories: [] }), + }, + }) + }) + + describe('with categories saved for current user', () => { + const saveCategorySettings = gql` + mutation ($activeCategories: [String]) { + saveCategorySettings(activeCategories: $activeCategories) + } + ` + beforeEach(async () => { + await mutate({ + mutation: saveCategorySettings, + variables: { activeCategories: ['cat1', 'cat3', 'cat5', 'cat7'] }, + }) + }) + + it('returns only the saved active categories', async () => { + const result = await query({ query: currentUserQuery, variables }) + expect(result.data.currentUser.activeCategories).toHaveLength(4) + expect(result.data.currentUser.activeCategories).toContain('cat1') + expect(result.data.currentUser.activeCategories).toContain('cat3') + expect(result.data.currentUser.activeCategories).toContain('cat5') + expect(result.data.currentUser.activeCategories).toContain('cat7') + }) + }) + }) }) }) }) diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js index 23a39a2a1..12f00ffb6 100644 --- a/backend/src/schema/resolvers/users.js +++ b/backend/src/schema/resolvers/users.js @@ -286,6 +286,10 @@ export default { { id }, ) }) + + // frontend gives [] when all categories are selected (default) + if (activeCategories.length === 0) return true + 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 7116d2201..d8fce3b29 100644 --- a/backend/src/schema/resolvers/users.spec.js +++ b/backend/src/schema/resolvers/users.spec.js @@ -602,9 +602,7 @@ describe('save category settings', () => { const userQuery = gql` query ($id: ID) { User(id: $id) { - activeCategories { - id - } + activeCategories } } ` @@ -631,11 +629,7 @@ describe('save category settings', () => { data: { User: [ { - activeCategories: expect.arrayContaining([ - { id: 'cat1' }, - { id: 'cat3' }, - { id: 'cat5' }, - ]), + activeCategories: expect.arrayContaining(['cat1', 'cat3', 'cat5']), }, ], }, @@ -678,11 +672,11 @@ describe('save category settings', () => { User: [ { activeCategories: expect.arrayContaining([ - { id: 'cat10' }, - { id: 'cat11' }, - { id: 'cat12' }, - { id: 'cat8' }, - { id: 'cat9' }, + 'cat10', + 'cat11', + 'cat12', + 'cat8', + 'cat9', ]), }, ], diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index 3d71aac78..2861b0fda 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -115,11 +115,11 @@ type User { emotions: [EMOTED] - activeCategories: [Category] @cypher( + activeCategories: [String] @cypher( statement: """ MATCH (category:Category) WHERE NOT ((this)-[:NOT_INTERESTED_IN]->(category)) - RETURN category + RETURN collect(category.id) """ ) diff --git a/package.json b/package.json index a36063f04..d7463adac 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "jsonwebtoken": "^8.5.1", "mock-socket": "^9.0.3", "neo4j-driver": "^4.3.4", - "neode": "^0.4.7", + "neode": "^0.4.8", "npm-run-all": "^4.1.5", "rosie": "^2.1.0", "slug": "^6.0.0" diff --git a/webapp/assets/_new/icons/svgs/culture.svg b/webapp/assets/_new/icons/svgs/culture.svg new file mode 100644 index 000000000..d63e38cb4 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/culture.svg @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/webapp/assets/_new/icons/svgs/energy.svg b/webapp/assets/_new/icons/svgs/energy.svg new file mode 100644 index 000000000..5035a5586 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/energy.svg @@ -0,0 +1,14 @@ + + + diff --git a/webapp/assets/_new/icons/svgs/finance.svg b/webapp/assets/_new/icons/svgs/finance.svg new file mode 100644 index 000000000..74081bc6a --- /dev/null +++ b/webapp/assets/_new/icons/svgs/finance.svg @@ -0,0 +1,13 @@ + + + + diff --git a/webapp/assets/_new/icons/svgs/health.svg b/webapp/assets/_new/icons/svgs/health.svg new file mode 100644 index 000000000..acf50d7c1 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/health.svg @@ -0,0 +1,7 @@ + + + + diff --git a/webapp/assets/_new/icons/svgs/media.svg b/webapp/assets/_new/icons/svgs/media.svg new file mode 100644 index 000000000..d63c98610 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/media.svg @@ -0,0 +1,7 @@ + + + + diff --git a/webapp/assets/_new/icons/svgs/miscellaneous.svg b/webapp/assets/_new/icons/svgs/miscellaneous.svg new file mode 100644 index 000000000..07f8dbe3f --- /dev/null +++ b/webapp/assets/_new/icons/svgs/miscellaneous.svg @@ -0,0 +1,13 @@ + + + + diff --git a/webapp/assets/_new/icons/svgs/mobility.svg b/webapp/assets/_new/icons/svgs/mobility.svg new file mode 100644 index 000000000..9e36ec21e --- /dev/null +++ b/webapp/assets/_new/icons/svgs/mobility.svg @@ -0,0 +1,11 @@ + + + diff --git a/webapp/assets/_new/icons/svgs/movement.svg b/webapp/assets/_new/icons/svgs/movement.svg index ac5cd9cc0..81052875d 100644 --- a/webapp/assets/_new/icons/svgs/movement.svg +++ b/webapp/assets/_new/icons/svgs/movement.svg @@ -1,20 +1,19 @@ - - - - - - - - - - - + + + + diff --git a/webapp/assets/_new/icons/svgs/nature.svg b/webapp/assets/_new/icons/svgs/nature.svg new file mode 100644 index 000000000..d40250af4 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/nature.svg @@ -0,0 +1,18 @@ + + + + diff --git a/webapp/assets/_new/icons/svgs/networking.svg b/webapp/assets/_new/icons/svgs/networking.svg new file mode 100644 index 000000000..b8d35da69 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/networking.svg @@ -0,0 +1,22 @@ + + + + + diff --git a/webapp/assets/_new/icons/svgs/peace.svg b/webapp/assets/_new/icons/svgs/peace.svg new file mode 100644 index 000000000..408601cae --- /dev/null +++ b/webapp/assets/_new/icons/svgs/peace.svg @@ -0,0 +1,8 @@ + + + diff --git a/webapp/assets/_new/icons/svgs/politics.svg b/webapp/assets/_new/icons/svgs/politics.svg new file mode 100644 index 000000000..35322097d --- /dev/null +++ b/webapp/assets/_new/icons/svgs/politics.svg @@ -0,0 +1,12 @@ + + + + + diff --git a/webapp/assets/_new/icons/svgs/psyche.svg b/webapp/assets/_new/icons/svgs/psyche.svg new file mode 100644 index 000000000..8c285d5ca --- /dev/null +++ b/webapp/assets/_new/icons/svgs/psyche.svg @@ -0,0 +1,8 @@ + + + diff --git a/webapp/assets/_new/icons/svgs/save.svg b/webapp/assets/_new/icons/svgs/save.svg new file mode 100644 index 000000000..31c1d8459 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/save.svg @@ -0,0 +1,5 @@ + + +save + + diff --git a/webapp/assets/_new/icons/svgs/science.svg b/webapp/assets/_new/icons/svgs/science.svg new file mode 100644 index 000000000..9d3211223 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/science.svg @@ -0,0 +1,25 @@ + + + + diff --git a/webapp/assets/_new/icons/svgs/spirituality.svg b/webapp/assets/_new/icons/svgs/spirituality.svg new file mode 100644 index 000000000..0c2757071 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/spirituality.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/webapp/components/FilterMenu/CategoriesFilter.spec.js b/webapp/components/FilterMenu/CategoriesFilter.spec.js index ef0f2ad90..813f7b33c 100644 --- a/webapp/components/FilterMenu/CategoriesFilter.spec.js +++ b/webapp/components/FilterMenu/CategoriesFilter.spec.js @@ -15,8 +15,19 @@ describe('CategoriesFilter.vue', () => { 'posts/filteredCategoryIds': jest.fn(() => []), } + const apolloMutationMock = jest.fn().mockResolvedValue({ + data: { saveCategorySettings: true }, + }) + const mocks = { $t: jest.fn((string) => string), + $apollo: { + mutate: apolloMutationMock, + }, + $toast: { + success: jest.fn(), + error: jest.fn(), + }, } const Wrapper = () => { @@ -76,5 +87,14 @@ describe('CategoriesFilter.vue', () => { expect(mutations['posts/RESET_CATEGORIES']).toHaveBeenCalledTimes(1) }) }) + + describe('save categories', () => { + it('calls the API', async () => { + wrapper = await Wrapper() + const saveButton = wrapper.findAll('.categories-filter .sidebar .base-button').at(1) + saveButton.trigger('click') + expect(apolloMutationMock).toBeCalled() + }) + }) }) }) diff --git a/webapp/components/FilterMenu/CategoriesFilter.vue b/webapp/components/FilterMenu/CategoriesFilter.vue index 47e4bcc10..6ed37d4b3 100644 --- a/webapp/components/FilterMenu/CategoriesFilter.vue +++ b/webapp/components/FilterMenu/CategoriesFilter.vue @@ -7,6 +7,8 @@ icon="check" @click="resetCategories" /> +
+