diff --git a/README.md b/README.md index d03b1e65..f3db19b6 100644 --- a/README.md +++ b/README.md @@ -128,21 +128,8 @@ Change DATABASE URL (e.g after a rollback) ### Backup -Create a backup +See [Docs](./docs/heroku-backup.md) -`heroku pg:backups:capture --app ` - -The following command will provide a URL to where the backup can be downloaded (expires after 60 minutes) - -`heroku pg:backups:url b001 --app ` - -To restore a backup, use - -`heroku pg:backups:restore b001 DATABASE_URL --app ` - -To see the backup schedule - -`heroku pg:backus:schedules --app ` ## AWS diff --git a/client/build/webpack.base.conf.js b/client/build/webpack.base.conf.js index b70630b9..81b79aca 100644 --- a/client/build/webpack.base.conf.js +++ b/client/build/webpack.base.conf.js @@ -2,7 +2,7 @@ const path = require('path'); const config = require('../config'); -const VueLoaderPlugin = require('vue-loader/lib/plugin'); +const {VueLoaderPlugin} = require('vue-loader'); const {isDev, styleRule, assetsPath} = require('./utils'); @@ -42,7 +42,7 @@ module.exports = { alias: { '@': resolve('src'), styles: resolve('src/styles'), - gql: resolve('src/graphql/gql') + gql: resolve('src/graphql/gql'), }, }, module: { @@ -64,9 +64,9 @@ module.exports = { test: /\.tsx?$/, loader: 'ts-loader', options: { - appendTsSuffixTo: [/\.vue$/] + appendTsSuffixTo: [/\.vue$/], }, - exclude: /node_modules/ + exclude: /node_modules/, }, { test: /\.js$/, @@ -79,7 +79,7 @@ module.exports = { { test: /\.(gql|graphql)$/, loader: 'graphql-tag/loader', - exclude: /node_modules/ + exclude: /node_modules/, }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, diff --git a/client/cypress/fixtures/mocks.js b/client/cypress/fixtures/mocks.js index eac0e79d..61cddee1 100644 --- a/client/cypress/fixtures/mocks.js +++ b/client/cypress/fixtures/mocks.js @@ -20,11 +20,13 @@ const classMemberIdIterator = idGenerator('ClassMemberNode'); const chapterIdIterator = idGenerator('ChapterNode'); const moduleIdIterator = idGenerator('ModuleNode'); const contentBlockIdIterator = idGenerator('ContentBlockNode'); +const instrumentIdGenerator = idGenerator('InstrumentNode'); const getClassMemberId = () => classMemberIdIterator.next().value; const getChapterId = () => chapterIdIterator.next().value; const getModuleId = () => moduleIdIterator.next().value; const getContentBlockId = () => contentBlockIdIterator.next().value; +const getInstrumentId = () => instrumentIdGenerator.next().value; export default { UUID: () => '123-456-789', @@ -108,5 +110,11 @@ export default { RoomEntryNode: () => ({ title: 'A Room Entry', contents: [], + }), + InstrumentNode: () => ({ + contents: [], + title: 'instrument-title', + slug: 'instrument-slug', + id: getInstrumentId(), }) }; diff --git a/client/cypress/integration/frontend/instruments-page.spec.js b/client/cypress/integration/frontend/instruments-page.spec.js new file mode 100644 index 00000000..81362bb4 --- /dev/null +++ b/client/cypress/integration/frontend/instruments-page.spec.js @@ -0,0 +1,88 @@ +describe('Instruments Page', () => { + beforeEach(() => { + cy.setup(); + }); + + it('opens the instruments page', () => { + const analyse = { + name: 'Analyse', + category: 'LANGUAGE_COMMUNICATION', + type: 'analyse', + }; + + const argumentation = { + name: 'Argumentation', + category: 'LANGUAGE_COMMUNICATION', + type: 'argumentation', + }; + + const ethik = { + name: 'Ethik', + category: 'SOCIETY', + type: 'ethik', + }; + + cy.mockGraphqlOps({ + operations: { + MeQuery: {}, + InstrumentsQuery: { + instruments: [ + { + type: analyse, + title: 'Instrument: Analyse', + slug: 'analyse', + }, + { + type: argumentation, + title: 'Instrument: Argumentation', + slug: 'argumentation', + }, + { + type: ethik, + title: 'Instrument: Ethik', + slug: 'ethik', + } + ], + }, + InstrumentTypesQuery: { + instrumentTypes: [ + analyse, + argumentation, + { + name: 'Beschreibung', + category: 'LANGUAGE_COMMUNICATION', + type: 'beschreibung', + }, + ethik, + { + name: 'Identität und Sozialisation', + category: 'SOCIETY', + type: 'identitt-und-sozialisation', + }, + ], + }, + }, + }); + cy.visit('instruments/'); + + cy.getByDataCy('instrument').should('have.length', 3); + + cy.getByDataCy('filter-language-communication').click(); + cy.getByDataCy('instrument').should('have.length', 2); + + cy.getByDataCy('filter-society').click(); + cy.getByDataCy('instrument').should('have.length', 1); + + cy.getByDataCy('filter-interdisciplinary').click(); + cy.getByDataCy('instrument').should('have.length', 0); + + cy.getByDataCy('filter-analyse').click(); + cy.getByDataCy('instrument').should('have.length', 1); + + cy.getByDataCy('filter-ethik').click(); + cy.getByDataCy('instrument').should('have.length', 1); + + cy.getByDataCy('filter-all-instruments').click(); + cy.getByDataCy('instrument').should('have.length', 3); + }); +}); diff --git a/client/cypress/integration/frontend/sidebar.spec.js b/client/cypress/integration/frontend/sidebar.spec.js index b4802f59..9163015f 100644 --- a/client/cypress/integration/frontend/sidebar.spec.js +++ b/client/cypress/integration/frontend/sidebar.spec.js @@ -6,21 +6,34 @@ describe('Sidebar', () => { }); it('should open sidebar and stay open', () => { + const {me} = getMinimalMe({}); const operations = { - MeQuery: getMinimalMe({}), + MeQuery: { + me: { + ...me, + schoolClasses: { + edges: [ + ...me.schoolClasses.edges, + {node: {}}, + ], + }, + }, + }, ProjectsQuery: { - projects: [] - } + projects: [], + }, }; cy.mockGraphqlOps({ - operations + operations, }); cy.visit('/portfolio'); cy.getByDataCy('sidebar').should('not.exist'); cy.getByDataCy('user-widget-avatar').click(); cy.getByDataCy('sidebar').should('exist'); + cy.getByDataCy('class-selection').click(); + cy.getByDataCy('class-selection-entry').should('have.length', 2); cy.getByDataCy('close-profile-sidebar-link').click(); cy.getByDataCy('sidebar').should('not.exist'); }); diff --git a/client/package-lock.json b/client/package-lock.json index ac8c5195..7c08504f 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -43746,6 +43746,13 @@ "integrity": "sha512-q8GgAIPU7xHCsUhB1PUgR//8GoI0bUdMRUKd69jw2UcKy7pGuu0NbJsOGqdSpdpvhO4LY/dgqohPEkE1TrBwKQ==", "requires": { "vue": "^2.1.10" + }, + "dependencies": { + "vue": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz", + "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==" + } } }, "svgo": { @@ -45176,6 +45183,13 @@ "babel-preset-env": "^1.6.0", "rollup-plugin-babel": "^3.0.2", "vue": "^2.4.4" + }, + "dependencies": { + "vue": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz", + "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==" + } } }, "vuejs-logger": { diff --git a/client/src/components/instruments/FilterEntry.vue b/client/src/components/instruments/FilterEntry.vue new file mode 100644 index 00000000..8f0a56e6 --- /dev/null +++ b/client/src/components/instruments/FilterEntry.vue @@ -0,0 +1,152 @@ + + + + + diff --git a/client/src/components/instruments/FilterGroup.vue b/client/src/components/instruments/FilterGroup.vue new file mode 100644 index 00000000..ebd5ab7a --- /dev/null +++ b/client/src/components/instruments/FilterGroup.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/client/src/components/instruments/InstrumentEntry.vue b/client/src/components/instruments/InstrumentEntry.vue new file mode 100644 index 00000000..ea127195 --- /dev/null +++ b/client/src/components/instruments/InstrumentEntry.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/client/src/components/instruments/InstrumentFilter.vue b/client/src/components/instruments/InstrumentFilter.vue index 82f8e082..c7381d28 100644 --- a/client/src/components/instruments/InstrumentFilter.vue +++ b/client/src/components/instruments/InstrumentFilter.vue @@ -1,97 +1,90 @@ diff --git a/client/src/components/profile/InstrumentActivity.vue b/client/src/components/profile/InstrumentActivity.vue index 2c81fafb..1e2bcef0 100644 --- a/client/src/components/profile/InstrumentActivity.vue +++ b/client/src/components/profile/InstrumentActivity.vue @@ -32,6 +32,7 @@ import ActivityEntry from '@/components/profile/ActivityEntry'; import SCROLL_TO_MUTATION from '@/graphql/gql/local/mutations/scrollTo.gql'; + import instrumentType from '@/helpers/instrumentType'; export default { props: ['instrument', 'filter'], @@ -52,11 +53,7 @@ return this.applyFilter('bookmarks') ? this.instrument.bookmarks : []; }, type() { - if (this.instrument.type === 'LANGUAGE_COMMUNICATION') { - return 'Sprache & Kommunikation'; - } else { - return 'Gesellschaft'; - } + return instrumentType(this.instrument); } }, methods: { diff --git a/client/src/consts/instrument.consts.js b/client/src/consts/instrument.consts.js new file mode 100644 index 00000000..dfeed83e --- /dev/null +++ b/client/src/consts/instrument.consts.js @@ -0,0 +1,3 @@ +export const LANGUAGE_COMMUNICATION = 'LANGUAGE_COMMUNICATION'; +export const SOCIETY = 'SOCIETY'; +export const INTERDISCIPLINARY = 'INTERDISCIPLINARY'; diff --git a/client/src/graphql/client.js b/client/src/graphql/client.js index 2bfabfc8..ede1035c 100644 --- a/client/src/graphql/client.js +++ b/client/src/graphql/client.js @@ -24,6 +24,10 @@ const writeLocalCache = cache => { __typename: 'HelloEmail', email: '', }, + instrumentFilter: { + __typename: 'InstrumentFilter', + currentFilter: 'abc' + } }, }); }; diff --git a/client/src/graphql/gql/local/instrumentFiler.gql b/client/src/graphql/gql/local/instrumentFiler.gql new file mode 100644 index 00000000..fe00a4be --- /dev/null +++ b/client/src/graphql/gql/local/instrumentFiler.gql @@ -0,0 +1,5 @@ +query InstrumentFilter { + instrumentFilter @client { + currentFilter + } +} diff --git a/client/src/graphql/gql/local/mutations/setInstrumentFilter.gql b/client/src/graphql/gql/local/mutations/setInstrumentFilter.gql new file mode 100644 index 00000000..bfc35931 --- /dev/null +++ b/client/src/graphql/gql/local/mutations/setInstrumentFilter.gql @@ -0,0 +1,3 @@ +mutation($filter: String!) { + setInstrumentFilter(filter: $filter) @client +} diff --git a/client/src/graphql/gql/queries/instrumentTypesQuery.gql b/client/src/graphql/gql/queries/instrumentTypesQuery.gql new file mode 100644 index 00000000..78b8b0db --- /dev/null +++ b/client/src/graphql/gql/queries/instrumentTypesQuery.gql @@ -0,0 +1,7 @@ +query InstrumentTypesQuery { + instrumentTypes { + name + type + category + } +} diff --git a/client/src/graphql/gql/queries/instrumentsByTypeQuery.gql b/client/src/graphql/gql/queries/instrumentsByTypeQuery.gql deleted file mode 100644 index 6024c799..00000000 --- a/client/src/graphql/gql/queries/instrumentsByTypeQuery.gql +++ /dev/null @@ -1,12 +0,0 @@ -query InstrumentQuery($type: String!){ - instruments(type: $type) { - edges { - node { - id - title - contents - slug - } - } - } -} diff --git a/client/src/graphql/gql/queries/instrumentsQuery.gql b/client/src/graphql/gql/queries/instrumentsQuery.gql index bed2a520..290846b4 100644 --- a/client/src/graphql/gql/queries/instrumentsQuery.gql +++ b/client/src/graphql/gql/queries/instrumentsQuery.gql @@ -1,13 +1,14 @@ -query InstrumentQuery { +query InstrumentsQuery { instruments { - edges { - node { - id - title - contents - slug - type - } + id + title + contents + slug + type { + id + type + category + name } } } diff --git a/client/src/graphql/resolvers.js b/client/src/graphql/resolvers.js index 2dd4abeb..2d0c2620 100644 --- a/client/src/graphql/resolvers.js +++ b/client/src/graphql/resolvers.js @@ -1,6 +1,7 @@ import SCROLL_POSITION from '@/graphql/gql/local/scrollPosition.gql'; import HELLO_EMAIL from '@/graphql/gql/local/helloEmail.gql'; import SIDEBAR from '@/graphql/gql/local/sidebar.gql'; +import INSTRUMENT_FILTER from '@/graphql/gql/local/instrumentFiler.gql'; export const resolvers = { Mutation: { @@ -16,6 +17,12 @@ export const resolvers = { cache.writeQuery({query: HELLO_EMAIL, data}); return data.helloEmail; }, + setInstrumentFilter: (_, {filter}, {cache}) => { + const data = cache.readQuery({query: INSTRUMENT_FILTER}); + data.instrumentFilter.currentFilter = filter; + cache.writeQuery({query: INSTRUMENT_FILTER, data}); + return data.instrumentFilter; + }, toggleSidebar: (_, {sidebar: {profile, navigation}}, {cache}) => { const data = cache.readQuery({query: SIDEBAR}); if (typeof profile !== 'undefined') { @@ -26,6 +33,6 @@ export const resolvers = { } cache.writeQuery({query: SIDEBAR, data}); return data.sidebar; - } - } + }, + }, }; diff --git a/client/src/graphql/typedefs.js b/client/src/graphql/typedefs.js index ecad752c..9528fc5e 100644 --- a/client/src/graphql/typedefs.js +++ b/client/src/graphql/typedefs.js @@ -19,6 +19,10 @@ export const typeDefs = gql` profile: Boolean } + type InstrumentFilter { + currentFilter: String! + } + type Mutation { scrollTo(scrollTo: String!): ScrollPosition helloEmail(email: String!): HelloEmail diff --git a/client/src/helpers/instrumentType.js b/client/src/helpers/instrumentType.js new file mode 100644 index 00000000..68f2d6d9 --- /dev/null +++ b/client/src/helpers/instrumentType.js @@ -0,0 +1,13 @@ +import {LANGUAGE_COMMUNICATION, SOCIETY} from '@/consts/instrument.consts'; + +const instrumentType = ({type: {category}}) => { + if (category === LANGUAGE_COMMUNICATION) { + return 'Sprache & Kommunikation'; + } else if (category === SOCIETY) { + return 'Gesellschaft'; + } else { + return 'Überfachliches Instrument'; + } +}; + +export default instrumentType; diff --git a/client/src/pages/instrumentOverview.vue b/client/src/pages/instrumentOverview.vue index c28387de..a339e23b 100644 --- a/client/src/pages/instrumentOverview.vue +++ b/client/src/pages/instrumentOverview.vue @@ -1,10 +1,5 @@