From b179affd37c491edc854a3df1580a05f353c3b8f Mon Sep 17 00:00:00 2001 From: Santiago Date: Tue, 20 Apr 2021 15:15:21 -0300 Subject: [PATCH 001/114] Initial test for front end component --- app/react/Templates/components/ViewTemplateAsPage.tsx | 3 +++ .../Templates/components/specs/MetadataTemplate.spec.js | 6 ++++++ 2 files changed, 9 insertions(+) create mode 100644 app/react/Templates/components/ViewTemplateAsPage.tsx diff --git a/app/react/Templates/components/ViewTemplateAsPage.tsx b/app/react/Templates/components/ViewTemplateAsPage.tsx new file mode 100644 index 0000000000..c029ef7d46 --- /dev/null +++ b/app/react/Templates/components/ViewTemplateAsPage.tsx @@ -0,0 +1,3 @@ +import React from 'react'; + +export const ViewTemplateAsPage = () => <>Placeholder; diff --git a/app/react/Templates/components/specs/MetadataTemplate.spec.js b/app/react/Templates/components/specs/MetadataTemplate.spec.js index 09a900e9d7..6d5fe66346 100644 --- a/app/react/Templates/components/specs/MetadataTemplate.spec.js +++ b/app/react/Templates/components/specs/MetadataTemplate.spec.js @@ -19,6 +19,7 @@ import { } from 'app/Templates/components/MetadataTemplate'; import MetadataProperty from 'app/Templates/components/MetadataProperty'; import { dragSource } from 'app/Templates/components/PropertyOption'; +import { ViewTemplateAsPage } from '../ViewTemplateAsPage'; function sourceTargetTestContext(Target, Source, actions) { return DragDropContext(TestBackend)( @@ -133,6 +134,11 @@ describe('MetadataTemplate', () => { expect(component.find(Control).first()).toMatchSnapshot(); }); + it('should render a ViewTemplateAsPage component', () => { + const component = shallow(); + expect(component.contains()).toBe(true); + }); + describe('when fields is empty', () => { it('should render a blank state', () => { const component = shallow(); From f93127bdaa2442eef863b8a1a6e8d57fd6cf9aeb Mon Sep 17 00:00:00 2001 From: Santiago Date: Tue, 20 Apr 2021 15:22:19 -0300 Subject: [PATCH 002/114] Added component with placeholder contents --- app/react/Templates/components/MetadataTemplate.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/react/Templates/components/MetadataTemplate.tsx b/app/react/Templates/components/MetadataTemplate.tsx index a78d1989c2..0dacc72278 100644 --- a/app/react/Templates/components/MetadataTemplate.tsx +++ b/app/react/Templates/components/MetadataTemplate.tsx @@ -24,6 +24,7 @@ import { bindActionCreators } from 'redux'; import { PropertySchema } from 'shared/types/commonTypes'; import { Icon } from 'UI'; +import { ViewTemplateAsPage } from './ViewTemplateAsPage'; import validator from './ValidateTemplate'; // eslint-disable-line import/no-named-as-default, import/no-named-as-default-member interface MetadataTemplateProps { @@ -113,6 +114,7 @@ export class MetadataTemplate extends Component { this.props._id )} > +
From 7f1088dffc0ddb6aeb6c353f8ece0864aef7f66b Mon Sep 17 00:00:00 2001 From: Santiago Date: Tue, 20 Apr 2021 16:09:29 -0300 Subject: [PATCH 003/114] Initial layout of frontend component --- .../Templates/components/MetadataTemplate.tsx | 3 +-- .../components/ViewTemplateAsPage.tsx | 17 ++++++++++++++++- .../specs/ViewTemplateAsPage.spec.tsx | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx diff --git a/app/react/Templates/components/MetadataTemplate.tsx b/app/react/Templates/components/MetadataTemplate.tsx index 0dacc72278..b4fe9786cc 100644 --- a/app/react/Templates/components/MetadataTemplate.tsx +++ b/app/react/Templates/components/MetadataTemplate.tsx @@ -114,7 +114,6 @@ export class MetadataTemplate extends Component { this.props._id )} > -
@@ -132,7 +131,7 @@ export class MetadataTemplate extends Component { /> )}
- + {connectDropTarget(
    diff --git a/app/react/Templates/components/ViewTemplateAsPage.tsx b/app/react/Templates/components/ViewTemplateAsPage.tsx index c029ef7d46..6e96d6a2ca 100644 --- a/app/react/Templates/components/ViewTemplateAsPage.tsx +++ b/app/react/Templates/components/ViewTemplateAsPage.tsx @@ -1,3 +1,18 @@ import React from 'react'; -export const ViewTemplateAsPage = () => <>Placeholder; +import { Tip } from 'app/Layout'; + +import { ToggleChildren } from 'app/Settings/components/ToggleChildren'; + +export const ViewTemplateAsPage = () => ( +
    + + +
    +); diff --git a/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx b/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx new file mode 100644 index 0000000000..3bc49374ab --- /dev/null +++ b/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; + +import { ViewTemplateAsPage } from '../ViewTemplateAsPage'; + +describe('ViewTemplateAsPage', () => { + let component: ShallowWrapper; + + beforeEach(() => { + component = shallow(); + }); + + it('should contain a label with an info icon and a ToggleChildren component', () => { + expect(component.find('label')).toHaveLength(1); + expect(component.find('Tip')).toHaveLength(1); + expect(component.find('ToggleChildren')).toHaveLength(1); + }); +}); From 0a008db1f5fedaae5a70fe5c58f303c9494bc820 Mon Sep 17 00:00:00 2001 From: Santiago Date: Tue, 20 Apr 2021 16:21:54 -0300 Subject: [PATCH 004/114] fleshing out component --- .../Templates/components/ViewTemplateAsPage.tsx | 16 ++++++++++++---- .../components/specs/ViewTemplateAsPage.spec.tsx | 5 +---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/app/react/Templates/components/ViewTemplateAsPage.tsx b/app/react/Templates/components/ViewTemplateAsPage.tsx index 6e96d6a2ca..c36a14a1c8 100644 --- a/app/react/Templates/components/ViewTemplateAsPage.tsx +++ b/app/react/Templates/components/ViewTemplateAsPage.tsx @@ -3,16 +3,24 @@ import React from 'react'; import { Tip } from 'app/Layout'; import { ToggleChildren } from 'app/Settings/components/ToggleChildren'; +import { t } from 'app/I18N'; export const ViewTemplateAsPage = () => (
    - + + +
    ); diff --git a/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx b/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx index 3bc49374ab..df85cd6d58 100644 --- a/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx +++ b/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx @@ -6,11 +6,8 @@ import { ViewTemplateAsPage } from '../ViewTemplateAsPage'; describe('ViewTemplateAsPage', () => { let component: ShallowWrapper; - beforeEach(() => { - component = shallow(); - }); - it('should contain a label with an info icon and a ToggleChildren component', () => { + component = shallow(); expect(component.find('label')).toHaveLength(1); expect(component.find('Tip')).toHaveLength(1); expect(component.find('ToggleChildren')).toHaveLength(1); From 984610a20df3fd3dc957f9e131736f74c0c8c274 Mon Sep 17 00:00:00 2001 From: RafaPolit Date: Wed, 21 Apr 2021 11:20:48 -0500 Subject: [PATCH 005/114] Migrated pagesModel to TS and json schema --- app/api/pages/pagesModel.js | 17 ----------------- app/api/pages/pagesModel.ts | 16 ++++++++++++++++ app/shared/types/pageSchema.ts | 33 +++++++++++++++++++++++++++++++++ app/shared/types/pageType.d.ts | 20 ++++++++++++++++++++ 4 files changed, 69 insertions(+), 17 deletions(-) delete mode 100644 app/api/pages/pagesModel.js create mode 100644 app/api/pages/pagesModel.ts create mode 100644 app/shared/types/pageSchema.ts create mode 100644 app/shared/types/pageType.d.ts diff --git a/app/api/pages/pagesModel.js b/app/api/pages/pagesModel.js deleted file mode 100644 index 820603cb2f..0000000000 --- a/app/api/pages/pagesModel.js +++ /dev/null @@ -1,17 +0,0 @@ -import mongoose from 'mongoose'; - -import { instanceModel } from 'api/odm'; - -const pagesSchema = new mongoose.Schema({ - title: String, - language: String, - sharedId: String, - creationDate: { type: Number, select: false }, - metadata: new mongoose.Schema({ - content: String, - script: String, - }), - user: { type: mongoose.Schema.Types.ObjectId, ref: 'users', select: false }, -}); - -export default instanceModel('pages', pagesSchema); diff --git a/app/api/pages/pagesModel.ts b/app/api/pages/pagesModel.ts new file mode 100644 index 0000000000..499300de69 --- /dev/null +++ b/app/api/pages/pagesModel.ts @@ -0,0 +1,16 @@ +import mongoose from 'mongoose'; +import { instanceModel } from 'api/odm'; +import { PageSchema } from 'shared/types/pageType'; + +const propsWithDBSpecifics = { + creationDate: { type: Number, select: false }, + user: { type: mongoose.Schema.Types.ObjectId, ref: 'users', select: false }, +}; + +const mongoSchema = new mongoose.Schema(propsWithDBSpecifics, { + strict: false, +}); + +const Model = instanceModel('pages', mongoSchema); + +export default Model; diff --git a/app/shared/types/pageSchema.ts b/app/shared/types/pageSchema.ts new file mode 100644 index 0000000000..13d211317e --- /dev/null +++ b/app/shared/types/pageSchema.ts @@ -0,0 +1,33 @@ +import { objectIdSchema } from 'shared/types/commonSchemas'; + +export const emitSchemaTypes = true; + +export const PageSchema = { + $schema: 'http://json-schema.org/schema#', + $async: true, + type: 'object', + additionalProperties: false, + title: 'PageSchema', + definitions: { objectIdSchema }, + properties: { + _id: objectIdSchema, + title: { type: 'string' }, + language: { type: 'string' }, + sharedId: { type: 'string' }, + creationDate: { type: 'number' }, + metadata: { + type: 'object', + additionalProperties: false, + definitions: { objectIdSchema }, + properties: { + _id: objectIdSchema, + content: { type: 'string' }, + script: { type: 'string' }, + }, + }, + user: objectIdSchema, + entityView: { type: 'boolean' }, + __v: { type: 'number' }, + }, + required: ['title'], +}; diff --git a/app/shared/types/pageType.d.ts b/app/shared/types/pageType.d.ts new file mode 100644 index 0000000000..d62b2e6fee --- /dev/null +++ b/app/shared/types/pageType.d.ts @@ -0,0 +1,20 @@ +/* eslint-disable */ +/**AUTO-GENERATED. RUN yarn emit-types to update.*/ + +import { ObjectIdSchema } from 'shared/types/commonTypes'; + +export interface PageSchema { + _id?: ObjectIdSchema; + title: string; + language?: string; + sharedId?: string; + creationDate?: number; + metadata?: { + _id?: ObjectIdSchema; + content?: string; + script?: string; + }; + user?: ObjectIdSchema; + entityView?: boolean; + __v?: number; +} From a9af7191b436a62d3b70fe2a0cb73a2c487c8342 Mon Sep 17 00:00:00 2001 From: RafaPolit Date: Wed, 21 Apr 2021 11:21:00 -0500 Subject: [PATCH 006/114] Foreign change due to emit types. --- app/shared/types/entityType.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/shared/types/entityType.d.ts b/app/shared/types/entityType.d.ts index 06c94bde5c..4cee764525 100644 --- a/app/shared/types/entityType.d.ts +++ b/app/shared/types/entityType.d.ts @@ -34,6 +34,7 @@ export type EntityWithFilesSchema = { title?: string; template?: ObjectIdSchema; published?: boolean; + generatedToc?: boolean; icon?: { _id?: string | null; label?: string; From d0b374ed7defd407f44221fa20ccf5f87b1db55b Mon Sep 17 00:00:00 2001 From: RafaPolit Date: Wed, 21 Apr 2021 12:13:00 -0500 Subject: [PATCH 007/114] Changed pages spec to TS and async --- app/api/pages/pages.js | 22 +++--- app/api/pages/specs/pages.spec.js | 127 ------------------------------ app/api/pages/specs/pages.spec.ts | 101 ++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 138 deletions(-) delete mode 100644 app/api/pages/specs/pages.spec.js create mode 100644 app/api/pages/specs/pages.spec.ts diff --git a/app/api/pages/pages.js b/app/api/pages/pages.js index 9c906f9bc5..2d6eea9728 100644 --- a/app/api/pages/pages.js +++ b/app/api/pages/pages.js @@ -6,7 +6,7 @@ import model from './pagesModel'; import settings from '../settings'; export default { - save(doc, user, language) { + async save(doc, user, language) { if (!doc.sharedId) { doc.user = user._id; doc.creationDate = date.currentUTC(); @@ -16,16 +16,16 @@ export default { return model.save(doc); } - return settings.get().then(({ languages }) => { - const sharedId = ID(); - const docs = languages.map(lang => ({ - ...doc, - language: lang.key, - sharedId, - })); - - return model.saveMultiple(docs).then(() => this.getById(sharedId, language)); - }); + const { languages } = await settings.get(); + const sharedId = ID(); + const docs = languages.map(lang => ({ + ...doc, + language: lang.key, + sharedId, + })); + + await model.saveMultiple(docs); + return this.getById(sharedId, language); }, get(query, select) { diff --git a/app/api/pages/specs/pages.spec.js b/app/api/pages/specs/pages.spec.js deleted file mode 100644 index a6c6a269cd..0000000000 --- a/app/api/pages/specs/pages.spec.js +++ /dev/null @@ -1,127 +0,0 @@ -import { catchErrors } from 'api/utils/jasmineHelpers'; -import { mockID } from 'shared/uniqueID'; -import date from 'api/utils/date.js'; -import db from 'api/utils/testing_db'; - -import fixtures, { pageToUpdate } from './fixtures.js'; -import pages from '../pages.js'; - -describe('pages', () => { - beforeEach(done => { - db.clearAllAndLoad(fixtures) - .then(done) - .catch(catchErrors(done)); - }); - - afterAll(done => { - db.disconnect().then(done); - }); - - describe('save', () => { - it('should create a new document with logged user id and UTC date for each language', done => { - spyOn(date, 'currentUTC').and.returnValue(1); - mockID('sharedid'); - - const doc = { title: 'Batman begins' }; - const user = { _id: db.id() }; - - pages - .save(doc, user, 'es') - .then(result => - Promise.all([ - pages.getById(result.sharedId, 'es', 'title creationDate user'), - pages.getById(result.sharedId, 'en', 'title creationDate user'), - pages.getById(result.sharedId, 'pt', 'title creationDate user'), - ]) - ) - .then(([es, en, pt]) => { - expect(es.title).toBe(doc.title); - expect(en.title).toBe(doc.title); - expect(pt.title).toBe(doc.title); - expect(es.user.equals(user._id)).toBe(true); - expect(es.creationDate).toEqual(1); - done(); - }) - .catch(catchErrors(done)); - }); - - it('should return the newly created document', done => { - const doc = { title: 'the dark knight' }; - const user = { _id: db.id() }; - - pages - .save(doc, user, 'es') - .then(createdDocument => { - expect(createdDocument._id.toString()).toBeDefined(); - expect(createdDocument.title).toBe(doc.title); - expect(createdDocument.language).toBe('es'); - return pages.get(createdDocument._id, 'creationDate user'); - }) - .then(([createdDocument]) => { - expect(createdDocument.user.equals(user._id)).toBe(true); - done(); - }) - .catch(catchErrors(done)); - }); - - describe('when updating', () => { - it('should not assign again user and creation date and partial update data', done => { - spyOn(date, 'currentUTC').and.returnValue(10); - - return pages - .save({ _id: pageToUpdate, sharedId: '1', title: 'Edited title' }, 'another_user') - .then(modifiedDoc => { - expect(modifiedDoc.title).toBe('Edited title'); - return pages.get(modifiedDoc._id, 'creationDate user'); - }) - .then(([doc]) => { - expect(doc.user).not.toBe('another_user'); - expect(doc.creationDate).toBe(1); - done(); - }) - .catch(catchErrors(done)); - }); - }); - }); - - describe('delete', () => { - it('should delete the document in all languages', done => { - const sharedId = '1'; - return pages - .delete(sharedId) - .then(() => pages.get({ sharedId })) - .then(result => { - expect(result.length).toBe(0); - done(); - }) - .catch(catchErrors(done)); - }); - }); - - describe('addLanguage()', () => { - it('should duplicate all the pages from the default language to the new one', done => { - pages - .addLanguage('ab') - .then(() => pages.get({ language: 'ab' })) - .then(newPages => { - expect(newPages.length).toBe(2); - done(); - }) - .catch(catchErrors(done)); - }); - }); - - describe('getById', () => { - it('Throws 404 error on unexistent id', done => { - pages - .getById('unexistent_id') - .then(() => { - done.fail('It should throw and error'); - }) - .catch(error => { - expect(error.code).toBe(404); - done(); - }); - }); - }); -}); diff --git a/app/api/pages/specs/pages.spec.ts b/app/api/pages/specs/pages.spec.ts new file mode 100644 index 0000000000..b4d88a1ff9 --- /dev/null +++ b/app/api/pages/specs/pages.spec.ts @@ -0,0 +1,101 @@ +import { catchErrors } from 'api/utils/jasmineHelpers'; +import { mockID } from 'shared/uniqueID'; +import date from 'api/utils/date.js'; +import db from 'api/utils/testing_db'; + +import fixtures, { pageToUpdate } from './fixtures.js'; +import pages from '../pages.js'; + +describe('pages', () => { + beforeEach(done => { + db.clearAllAndLoad(fixtures) + .then(done) + .catch(catchErrors(done)); + }); + + afterAll(async () => { + await db.disconnect(); + }); + + describe('save', () => { + it('should create a new document with logged user id and UTC date for each language', async () => { + spyOn(date, 'currentUTC').and.returnValue(1); + mockID('sharedid'); + + const doc = { title: 'Batman begins' }; + const user = { _id: db.id() }; + + const result = await pages.save(doc, user, 'es'); + const [es, en, pt] = await Promise.all([ + pages.getById(result.sharedId, 'es', 'title creationDate user'), + pages.getById(result.sharedId, 'en', 'title creationDate user'), + pages.getById(result.sharedId, 'pt', 'title creationDate user'), + ]); + + expect([es.title, en.title, pt.title]).toEqual([doc.title, doc.title, doc.title]); + expect(es.user?.toString()).toBe(user._id.toString()); + expect(es.creationDate).toEqual(1); + }); + + it('should return the newly created page', async () => { + const page = { title: 'the dark knight' }; + const user = { _id: db.id() }; + + const createdPage = await pages.save(page, user, 'es'); + + expect(createdPage._id.toString()).toBeDefined(); + expect(createdPage.title).toBe(page.title); + expect(createdPage.language).toBe('es'); + + const [pageInDB] = await pages.get(createdPage._id, 'creationDate user'); + + expect(pageInDB.user?.toString()).toBe(user._id.toString()); + }); + + describe('when updating', () => { + it('should not assign again user and creation date and partial update data', async () => { + spyOn(date, 'currentUTC').and.returnValue(10); + + const modifiedDoc = await pages.save( + { _id: pageToUpdate, sharedId: '1', title: 'Edited title' }, + 'another_user' + ); + + expect(modifiedDoc.title).toBe('Edited title'); + + const [doc] = await pages.get(modifiedDoc._id, 'creationDate user'); + + expect(doc.user).not.toBe('another_user'); + expect(doc.creationDate).toBe(1); + }); + }); + }); + + describe('delete', () => { + it('should delete the document in all languages', async () => { + const sharedId = '1'; + await pages.delete(sharedId); + const result = await pages.get({ sharedId }); + expect(result.length).toBe(0); + }); + }); + + describe('addLanguage()', () => { + it('should duplicate all the pages from the default language to the new one', async () => { + await pages.addLanguage('ab'); + const newPages = await pages.get({ language: 'ab' }); + expect(newPages.length).toBe(2); + }); + }); + + describe('getById', () => { + it('Throws 404 error on unexistent id', async () => { + expect.assertions(1); + try { + await pages.getById('unexistent_id'); + } catch (error) { + expect(error.code).toBe(404); + } + }); + }); +}); From 3e19fba6ac8e2da0aad27ba14bd2b615874db539 Mon Sep 17 00:00:00 2001 From: RafaPolit Date: Wed, 21 Apr 2021 13:18:09 -0500 Subject: [PATCH 008/114] Migrated pages.js to TS --- app/api/pages/pages.js | 84 ----------------------------- app/api/pages/pages.ts | 89 +++++++++++++++++++++++++++++++ app/api/pages/specs/pages.spec.ts | 36 ++++++++----- 3 files changed, 113 insertions(+), 96 deletions(-) delete mode 100644 app/api/pages/pages.js create mode 100644 app/api/pages/pages.ts diff --git a/app/api/pages/pages.js b/app/api/pages/pages.js deleted file mode 100644 index 2d6eea9728..0000000000 --- a/app/api/pages/pages.js +++ /dev/null @@ -1,84 +0,0 @@ -import { createError } from 'api/utils'; -import ID from 'shared/uniqueID'; -import date from 'api/utils/date.js'; - -import model from './pagesModel'; -import settings from '../settings'; - -export default { - async save(doc, user, language) { - if (!doc.sharedId) { - doc.user = user._id; - doc.creationDate = date.currentUTC(); - } - - if (doc.sharedId) { - return model.save(doc); - } - - const { languages } = await settings.get(); - const sharedId = ID(); - const docs = languages.map(lang => ({ - ...doc, - language: lang.key, - sharedId, - })); - - await model.saveMultiple(docs); - return this.getById(sharedId, language); - }, - - get(query, select) { - return model.get(query, select); - }, - - getById(sharedId, language, select) { - return this.get({ sharedId, language }, select).then(results => - results[0] ? results[0] : Promise.reject(createError('Page not found', 404)) - ); - }, - - delete(sharedId) { - return model.delete({ sharedId }); - }, - - async addLanguage(language) { - const [lanuageTranslationAlreadyExists] = await this.get({ locale: language }, null, { - limit: 1, - }); - if (lanuageTranslationAlreadyExists) { - return Promise.resolve(); - } - - const { languages } = await settings.get(); - - const defaultLanguage = languages.find(l => l.default).key; - const duplicate = (offset, totalRows) => { - const limit = 200; - if (offset >= totalRows) { - return Promise.resolve(); - } - - return this.get({ language: defaultLanguage }, null, { skip: offset, limit }) - .then(pages => { - const savePages = pages.map(_page => { - const page = { ..._page, language }; - delete page._id; - delete page.__v; - return this.save(page); - }); - - return Promise.all(savePages); - }) - .then(() => duplicate(offset + limit, totalRows)); - }; - - return this.count({ language: defaultLanguage }).then(totalRows => duplicate(0, totalRows)); - }, - - async removeLanguage(language) { - return model.delete({ language }); - }, - - count: model.count.bind(model), -}; diff --git a/app/api/pages/pages.ts b/app/api/pages/pages.ts new file mode 100644 index 0000000000..ea1701462c --- /dev/null +++ b/app/api/pages/pages.ts @@ -0,0 +1,89 @@ +import { createError } from 'api/utils'; +import ID from 'shared/uniqueID'; +import date from 'api/utils/date.js'; +import { UwaziFilterQuery } from 'api/odm'; +import { PageSchema } from 'shared/types/pageType'; +import { User } from 'api/users/usersModel'; + +import model from './pagesModel'; +import settings from '../settings'; + +const assignUserAndDate = (page: PageSchema, user?: User) => { + if (!user) { + throw new Error('missing user'); + } + return { + ...page, + user: user._id, + creationDate: date.currentUTC(), + }; +}; + +export default { + async save(_page: PageSchema, user?: User, language?: string) { + let page = { ..._page }; + if (!page.sharedId) { + page = assignUserAndDate(page, user); + } + + if (page.sharedId) { + return model.save(page); + } + + const { languages = [] } = await settings.get(); + const sharedId = ID(); + const pages = languages.map(lang => ({ + ...page, + language: lang.key, + sharedId, + })); + + await model.saveMultiple(pages); + return this.getById(sharedId, language); + }, + + get(query: UwaziFilterQuery, select?: string) { + return model.get(query, select); + }, + + async getById(sharedId: string, language?: string, select?: string) { + const results = await this.get({ sharedId, language }, select); + return results[0] ? results[0] : Promise.reject(createError('Page not found', 404)); + }, + + async delete(sharedId: string) { + return model.delete({ sharedId }); + }, + + async addLanguage(language: string) { + const [lanuageTranslationAlreadyExists] = await this.get({ locale: language }); + if (lanuageTranslationAlreadyExists) { + return Promise.resolve(); + } + + const { languages } = await settings.get(); + + const defaultLanguage = languages?.find(l => l.default)?.key; + + const duplicate = async () => { + const pages = await this.get({ language: defaultLanguage }); + const savePages = pages.map(async _page => { + const page = { ..._page, language }; + delete page._id; + delete page.__v; + return this.save(page); + }); + + return Promise.all(savePages); + }; + + return duplicate(); + }, + + // TEST!!! + async removeLanguage(language: string) { + return model.delete({ language }); + }, + + count: model.count.bind(model), +}; diff --git a/app/api/pages/specs/pages.spec.ts b/app/api/pages/specs/pages.spec.ts index b4d88a1ff9..e3a08bab21 100644 --- a/app/api/pages/specs/pages.spec.ts +++ b/app/api/pages/specs/pages.spec.ts @@ -3,8 +3,8 @@ import { mockID } from 'shared/uniqueID'; import date from 'api/utils/date.js'; import db from 'api/utils/testing_db'; -import fixtures, { pageToUpdate } from './fixtures.js'; -import pages from '../pages.js'; +import fixtures, { pageToUpdate } from './fixtures'; +import pages from '../pages'; describe('pages', () => { beforeEach(done => { @@ -18,25 +18,37 @@ describe('pages', () => { }); describe('save', () => { - it('should create a new document with logged user id and UTC date for each language', async () => { + it('should create a new page with logged user id and UTC date for each language', async () => { spyOn(date, 'currentUTC').and.returnValue(1); mockID('sharedid'); - const doc = { title: 'Batman begins' }; + const page = { title: 'Batman begins' }; const user = { _id: db.id() }; - const result = await pages.save(doc, user, 'es'); + const result = await pages.save(page, user, 'es'); + const sharedId = result.sharedId || ''; const [es, en, pt] = await Promise.all([ - pages.getById(result.sharedId, 'es', 'title creationDate user'), - pages.getById(result.sharedId, 'en', 'title creationDate user'), - pages.getById(result.sharedId, 'pt', 'title creationDate user'), + pages.getById(sharedId, 'es', 'title creationDate user'), + pages.getById(sharedId, 'en', 'title creationDate user'), + pages.getById(sharedId, 'pt', 'title creationDate user'), ]); - expect([es.title, en.title, pt.title]).toEqual([doc.title, doc.title, doc.title]); + expect([es.title, en.title, pt.title]).toEqual([page.title, page.title, page.title]); expect(es.user?.toString()).toBe(user._id.toString()); expect(es.creationDate).toEqual(1); }); + it('should fail if new page saved but no user passed', async () => { + expect.assertions(1); + const page = { title: 'Batman returns' }; + + try { + await pages.save(page); + } catch (err) { + expect(err.message).toBe('missing user'); + } + }); + it('should return the newly created page', async () => { const page = { title: 'the dark knight' }; const user = { _id: db.id() }; @@ -58,7 +70,7 @@ describe('pages', () => { const modifiedDoc = await pages.save( { _id: pageToUpdate, sharedId: '1', title: 'Edited title' }, - 'another_user' + { username: 'another_user' } ); expect(modifiedDoc.title).toBe('Edited title'); @@ -72,7 +84,7 @@ describe('pages', () => { }); describe('delete', () => { - it('should delete the document in all languages', async () => { + it('should delete the page in all languages', async () => { const sharedId = '1'; await pages.delete(sharedId); const result = await pages.get({ sharedId }); @@ -89,7 +101,7 @@ describe('pages', () => { }); describe('getById', () => { - it('Throws 404 error on unexistent id', async () => { + it('should throws 404 error on unexistent id', async () => { expect.assertions(1); try { await pages.getById('unexistent_id'); From 800eb99e207a1f37a25867a026fcb008411d504b Mon Sep 17 00:00:00 2001 From: RafaPolit Date: Wed, 21 Apr 2021 14:01:45 -0500 Subject: [PATCH 009/114] Moving routes to TS, lacking validation --- app/api/pages/{routes.js => routes.ts} | 34 +++++------------- .../specs/__snapshots__/routes.spec.ts.snap | 35 +++++++++++++++++++ app/shared/types/pageType.d.ts | 1 - 3 files changed, 44 insertions(+), 26 deletions(-) rename app/api/pages/{routes.js => routes.ts} (67%) create mode 100644 app/api/pages/specs/__snapshots__/routes.spec.ts.snap diff --git a/app/api/pages/routes.js b/app/api/pages/routes.ts similarity index 67% rename from app/api/pages/routes.js rename to app/api/pages/routes.ts index d232c0f4b6..56edf14c37 100644 --- a/app/api/pages/routes.js +++ b/app/api/pages/routes.ts @@ -1,38 +1,22 @@ import Joi from 'joi'; +import { Application, NextFunction, Request, Response } from 'express'; import { validation } from 'api/utils'; import needsAuthorization from '../auth/authMiddleware'; import pages from './pages'; -export default app => { +export default (app: Application) => { app.post( '/api/pages', + needsAuthorization(['admin']), - needsAuthorization(), - - validation.validateRequest( - Joi.object() - .keys({ - _id: Joi.objectId(), - __v: Joi.number(), - sharedId: Joi.string(), - title: Joi.string().required(), - language: Joi.string(), - metadata: Joi.object().keys({ - _id: Joi.objectId(), - content: Joi.string().allow(''), - script: Joi.string().allow(''), - }), - }) - .required() - ), - - (req, res, next) => + (req: Request, res: Response, next: NextFunction) => { pages .save(req.body, req.user, req.language) .then(response => res.json(response)) - .catch(next) + .catch(next); + } ); app.get( @@ -47,7 +31,7 @@ export default app => { 'query' ), - (req, res, next) => { + (req: Request, res: Response, next: NextFunction) => { pages .get({ ...req.query, language: req.language }) .then(res.json.bind(res)) @@ -67,7 +51,7 @@ export default app => { 'query' ), - (req, res, next) => { + (req: Request, res: Response, next: NextFunction) => { pages .getById(req.query.sharedId, req.language) .then(res.json.bind(res)) @@ -88,7 +72,7 @@ export default app => { 'query' ), - (req, res, next) => { + (req: Request, res: Response, next: NextFunction) => { pages .delete(req.query.sharedId) .then(response => res.json(response)) diff --git a/app/api/pages/specs/__snapshots__/routes.spec.ts.snap b/app/api/pages/specs/__snapshots__/routes.spec.ts.snap new file mode 100644 index 0000000000..a8449b6792 --- /dev/null +++ b/app/api/pages/specs/__snapshots__/routes.spec.ts.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Pages Routes DELETE should have a validation schema 1`] = ` +Object { + "children": Object { + "sharedId": Object { + "invalids": Array [ + "", + ], + "type": "string", + }, + }, + "flags": Object { + "presence": "required", + }, + "type": "object", +} +`; + +exports[`Pages Routes GET /api/pages should have a validation schema 1`] = ` +Object { + "children": Object { + "sharedId": Object { + "invalids": Array [ + "", + ], + "type": "string", + }, + }, + "flags": Object { + "presence": "required", + }, + "type": "object", +} +`; diff --git a/app/shared/types/pageType.d.ts b/app/shared/types/pageType.d.ts index d62b2e6fee..e7bf37aec9 100644 --- a/app/shared/types/pageType.d.ts +++ b/app/shared/types/pageType.d.ts @@ -11,7 +11,6 @@ export interface PageSchema { creationDate?: number; metadata?: { _id?: ObjectIdSchema; - content?: string; script?: string; }; user?: ObjectIdSchema; From 3816663fc74ab457f773bd1a56c3d66110ff455a Mon Sep 17 00:00:00 2001 From: RafaPolit Date: Thu, 22 Apr 2021 10:21:32 -0500 Subject: [PATCH 010/114] Preparing to test via supertest route validation --- app/api/pages/pages.ts | 8 +- .../specs/__snapshots__/routes.spec.js.snap | 84 ------------------- .../specs/__snapshots__/routes.spec.ts.snap | 35 -------- app/api/pages/specs/routes.spec.js | 6 +- app/shared/types/pageSchema.ts | 2 +- app/shared/types/pageType.d.ts | 3 +- 6 files changed, 8 insertions(+), 130 deletions(-) delete mode 100644 app/api/pages/specs/__snapshots__/routes.spec.ts.snap diff --git a/app/api/pages/pages.ts b/app/api/pages/pages.ts index ea1701462c..6c0e7fddda 100644 --- a/app/api/pages/pages.ts +++ b/app/api/pages/pages.ts @@ -2,13 +2,13 @@ import { createError } from 'api/utils'; import ID from 'shared/uniqueID'; import date from 'api/utils/date.js'; import { UwaziFilterQuery } from 'api/odm'; -import { PageSchema } from 'shared/types/pageType'; +import { PageType } from 'shared/types/pageType'; import { User } from 'api/users/usersModel'; import model from './pagesModel'; import settings from '../settings'; -const assignUserAndDate = (page: PageSchema, user?: User) => { +const assignUserAndDate = (page: PageType, user?: User) => { if (!user) { throw new Error('missing user'); } @@ -20,7 +20,7 @@ const assignUserAndDate = (page: PageSchema, user?: User) => { }; export default { - async save(_page: PageSchema, user?: User, language?: string) { + async save(_page: PageType, user?: User, language?: string) { let page = { ..._page }; if (!page.sharedId) { page = assignUserAndDate(page, user); @@ -42,7 +42,7 @@ export default { return this.getById(sharedId, language); }, - get(query: UwaziFilterQuery, select?: string) { + get(query: UwaziFilterQuery, select?: string) { return model.get(query, select); }, diff --git a/app/api/pages/specs/__snapshots__/routes.spec.js.snap b/app/api/pages/specs/__snapshots__/routes.spec.js.snap index 6827da52c3..a8449b6792 100644 --- a/app/api/pages/specs/__snapshots__/routes.spec.js.snap +++ b/app/api/pages/specs/__snapshots__/routes.spec.js.snap @@ -1,89 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Pages Routes /api/pages should have a validation schema 1`] = ` -Object { - "children": Object { - "__v": Object { - "invalids": Array [ - Infinity, - -Infinity, - ], - "type": "number", - }, - "_id": Object { - "invalids": Array [ - "", - ], - "rules": Array [ - Object { - "arg": Object { - "pattern": /\\^\\[0-9a-fA-F\\]\\{24\\}\\$/, - }, - "name": "regex", - }, - ], - "type": "string", - }, - "language": Object { - "invalids": Array [ - "", - ], - "type": "string", - }, - "metadata": Object { - "children": Object { - "_id": Object { - "invalids": Array [ - "", - ], - "rules": Array [ - Object { - "arg": Object { - "pattern": /\\^\\[0-9a-fA-F\\]\\{24\\}\\$/, - }, - "name": "regex", - }, - ], - "type": "string", - }, - "content": Object { - "type": "string", - "valids": Array [ - "", - ], - }, - "script": Object { - "type": "string", - "valids": Array [ - "", - ], - }, - }, - "type": "object", - }, - "sharedId": Object { - "invalids": Array [ - "", - ], - "type": "string", - }, - "title": Object { - "flags": Object { - "presence": "required", - }, - "invalids": Array [ - "", - ], - "type": "string", - }, - }, - "flags": Object { - "presence": "required", - }, - "type": "object", -} -`; - exports[`Pages Routes DELETE should have a validation schema 1`] = ` Object { "children": Object { diff --git a/app/api/pages/specs/__snapshots__/routes.spec.ts.snap b/app/api/pages/specs/__snapshots__/routes.spec.ts.snap deleted file mode 100644 index a8449b6792..0000000000 --- a/app/api/pages/specs/__snapshots__/routes.spec.ts.snap +++ /dev/null @@ -1,35 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Pages Routes DELETE should have a validation schema 1`] = ` -Object { - "children": Object { - "sharedId": Object { - "invalids": Array [ - "", - ], - "type": "string", - }, - }, - "flags": Object { - "presence": "required", - }, - "type": "object", -} -`; - -exports[`Pages Routes GET /api/pages should have a validation schema 1`] = ` -Object { - "children": Object { - "sharedId": Object { - "invalids": Array [ - "", - ], - "type": "string", - }, - }, - "flags": Object { - "presence": "required", - }, - "type": "object", -} -`; diff --git a/app/api/pages/specs/routes.spec.js b/app/api/pages/specs/routes.spec.js index 53a5c6dbc2..18a4c021db 100644 --- a/app/api/pages/specs/routes.spec.js +++ b/app/api/pages/specs/routes.spec.js @@ -2,7 +2,7 @@ import { catchErrors } from 'api/utils/jasmineHelpers'; import instrumentRoutes from '../../utils/instrumentRoutes'; import pages from '../pages'; -import pagesRoutes from '../routes.js'; +import pagesRoutes from '../routes'; describe('Pages Routes', () => { let routes; @@ -40,10 +40,6 @@ describe('Pages Routes', () => { }); describe('/api/pages', () => { - it('should have a validation schema', () => { - expect(routes.post.validation('/api/pages')).toMatchSnapshot(); - }); - it('should ask pages model for the page in the current locale', done => { const req = { query: { sharedId: '123' }, diff --git a/app/shared/types/pageSchema.ts b/app/shared/types/pageSchema.ts index 13d211317e..b3addeeb55 100644 --- a/app/shared/types/pageSchema.ts +++ b/app/shared/types/pageSchema.ts @@ -7,7 +7,7 @@ export const PageSchema = { $async: true, type: 'object', additionalProperties: false, - title: 'PageSchema', + title: 'PageType', definitions: { objectIdSchema }, properties: { _id: objectIdSchema, diff --git a/app/shared/types/pageType.d.ts b/app/shared/types/pageType.d.ts index e7bf37aec9..dba4ed7539 100644 --- a/app/shared/types/pageType.d.ts +++ b/app/shared/types/pageType.d.ts @@ -3,7 +3,7 @@ import { ObjectIdSchema } from 'shared/types/commonTypes'; -export interface PageSchema { +export interface PageType { _id?: ObjectIdSchema; title: string; language?: string; @@ -11,6 +11,7 @@ export interface PageSchema { creationDate?: number; metadata?: { _id?: ObjectIdSchema; + content?: string; script?: string; }; user?: ObjectIdSchema; From 31ec8232ba73bffe52bd32874d3ed902aa7c9f1b Mon Sep 17 00:00:00 2001 From: RafaPolit Date: Thu, 22 Apr 2021 10:58:12 -0500 Subject: [PATCH 011/114] Added route validation --- app/api/pages/routes.ts | 8 +++ ...js.snap => routesToBeUpdated.spec.js.snap} | 4 +- .../pages/specs/{fixtures.js => fixtures.ts} | 0 app/api/pages/specs/routes.spec.ts | 54 +++++++++++++++++++ ...utes.spec.js => routesToBeUpdated.spec.js} | 2 +- 5 files changed, 65 insertions(+), 3 deletions(-) rename app/api/pages/specs/__snapshots__/{routes.spec.js.snap => routesToBeUpdated.spec.js.snap} (72%) rename app/api/pages/specs/{fixtures.js => fixtures.ts} (100%) create mode 100644 app/api/pages/specs/routes.spec.ts rename app/api/pages/specs/{routes.spec.js => routesToBeUpdated.spec.js} (98%) diff --git a/app/api/pages/routes.ts b/app/api/pages/routes.ts index 56edf14c37..cdb5a34611 100644 --- a/app/api/pages/routes.ts +++ b/app/api/pages/routes.ts @@ -1,6 +1,7 @@ import Joi from 'joi'; import { Application, NextFunction, Request, Response } from 'express'; +import { PageSchema } from 'shared/types/pageSchema'; import { validation } from 'api/utils'; import needsAuthorization from '../auth/authMiddleware'; @@ -11,6 +12,13 @@ export default (app: Application) => { '/api/pages', needsAuthorization(['admin']), + validation.validateRequest({ + type: 'object', + properties: { + body: PageSchema, + }, + }), + (req: Request, res: Response, next: NextFunction) => { pages .save(req.body, req.user, req.language) diff --git a/app/api/pages/specs/__snapshots__/routes.spec.js.snap b/app/api/pages/specs/__snapshots__/routesToBeUpdated.spec.js.snap similarity index 72% rename from app/api/pages/specs/__snapshots__/routes.spec.js.snap rename to app/api/pages/specs/__snapshots__/routesToBeUpdated.spec.js.snap index a8449b6792..1f082777a4 100644 --- a/app/api/pages/specs/__snapshots__/routes.spec.js.snap +++ b/app/api/pages/specs/__snapshots__/routesToBeUpdated.spec.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Pages Routes DELETE should have a validation schema 1`] = ` +exports[`Pages Routes (to be updated) DELETE should have a validation schema 1`] = ` Object { "children": Object { "sharedId": Object { @@ -17,7 +17,7 @@ Object { } `; -exports[`Pages Routes GET /api/pages should have a validation schema 1`] = ` +exports[`Pages Routes (to be updated) GET /api/pages should have a validation schema 1`] = ` Object { "children": Object { "sharedId": Object { diff --git a/app/api/pages/specs/fixtures.js b/app/api/pages/specs/fixtures.ts similarity index 100% rename from app/api/pages/specs/fixtures.js rename to app/api/pages/specs/fixtures.ts diff --git a/app/api/pages/specs/routes.spec.ts b/app/api/pages/specs/routes.spec.ts new file mode 100644 index 0000000000..db28d264ff --- /dev/null +++ b/app/api/pages/specs/routes.spec.ts @@ -0,0 +1,54 @@ +import { Application, NextFunction, Request, Response } from 'express'; +import request from 'supertest'; + +import testingDB from 'api/utils/testing_db'; +import { setUpApp } from 'api/utils/testingRoutes'; + +import pagesRoutes from '../routes'; +import fixtures from './fixtures'; + +const getUser = () => ({ _username: 'user 1', role: 'admin' }); + +const app: Application = setUpApp( + pagesRoutes, + (req: Request, _res: Response, next: NextFunction) => { + (req as any).user = getUser(); + next(); + } +); + +describe('Pages Routes', () => { + beforeEach(async () => { + await testingDB.clearAllAndLoad(fixtures); + }); + + afterAll(async () => { + await testingDB.disconnect(); + }); + + describe('/api/pages', () => { + it('should validate with minimum required props', async () => { + const goodData = { title: 'good structure' }; + + const response = await request(app) + .post('/api/pages') + .set('X-Requested-With', 'XMLHttpRequest') + .set('content-language', 'en') + .send(goodData); + + expect(response.status).toBe(200); + }); + + it('should not validate with wrong structure', async () => { + const badData = { withoutTitle: true }; + + const response = await request(app) + .post('/api/pages') + .set('X-Requested-With', 'XMLHttpRequest') + .send(badData); + + expect(response.status).toBe(400); + expect(response.text).toContain('validation failed'); + }); + }); +}); diff --git a/app/api/pages/specs/routes.spec.js b/app/api/pages/specs/routesToBeUpdated.spec.js similarity index 98% rename from app/api/pages/specs/routes.spec.js rename to app/api/pages/specs/routesToBeUpdated.spec.js index 18a4c021db..1639fdbcbf 100644 --- a/app/api/pages/specs/routes.spec.js +++ b/app/api/pages/specs/routesToBeUpdated.spec.js @@ -4,7 +4,7 @@ import instrumentRoutes from '../../utils/instrumentRoutes'; import pages from '../pages'; import pagesRoutes from '../routes'; -describe('Pages Routes', () => { +describe('Pages Routes (to be updated)', () => { let routes; beforeEach(() => { From 4fd786f64bac83fa4b02597f1fb92548c0f7be74 Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 22 Apr 2021 14:24:27 -0300 Subject: [PATCH 012/114] Naming fix --- app/api/pages/pagesModel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/pages/pagesModel.ts b/app/api/pages/pagesModel.ts index 499300de69..b07567c439 100644 --- a/app/api/pages/pagesModel.ts +++ b/app/api/pages/pagesModel.ts @@ -1,6 +1,6 @@ import mongoose from 'mongoose'; import { instanceModel } from 'api/odm'; -import { PageSchema } from 'shared/types/pageType'; +import { PageType } from 'shared/types/pageType'; const propsWithDBSpecifics = { creationDate: { type: Number, select: false }, @@ -11,6 +11,6 @@ const mongoSchema = new mongoose.Schema(propsWithDBSpecifics, { strict: false, }); -const Model = instanceModel('pages', mongoSchema); +const Model = instanceModel('pages', mongoSchema); export default Model; From 958655747c2a739dd754b519d39c72d11ba8fa8f Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 22 Apr 2021 16:44:40 -0300 Subject: [PATCH 013/114] Test for new option --- app/react/Pages/components/specs/PageCreator.spec.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/react/Pages/components/specs/PageCreator.spec.js b/app/react/Pages/components/specs/PageCreator.spec.js index f05ce5535e..2ec84b603b 100644 --- a/app/react/Pages/components/specs/PageCreator.spec.js +++ b/app/react/Pages/components/specs/PageCreator.spec.js @@ -65,6 +65,12 @@ describe('PageCreator', () => { .parent() .props().model ).toBe('.metadata.script'); + expect( + component + .find('ToggleButton') + .parent() + .props().model + ).toBe('.entityView'); }); describe('when Title is invalid', () => { From 5b51b2ac180306ccc44632696bdaa6eb2d26fc00 Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 22 Apr 2021 16:45:26 -0300 Subject: [PATCH 014/114] Initial setup --- app/react/Pages/components/PageCreator.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/react/Pages/components/PageCreator.js b/app/react/Pages/components/PageCreator.js index 61f773abda..34c5215d73 100644 --- a/app/react/Pages/components/PageCreator.js +++ b/app/react/Pages/components/PageCreator.js @@ -11,8 +11,9 @@ import { } from 'app/Pages/actions/pageActions'; import ShowIf from 'app/App/ShowIf'; import { BackButton } from 'app/Layout'; -import { Icon } from 'UI'; +import { Icon, ToggleButton } from 'UI'; +import { Translate } from 'app/I18N'; import validator from './ValidatePage'; export class PageCreator extends Component { @@ -47,6 +48,12 @@ export class PageCreator extends Component {
+
+ Enable this page to be used as an entity view page: + + {}} /> + +
{pageUrl} From 128d8e79c3baa0d4575ea91bab4733163fd7d418 Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 22 Apr 2021 17:55:03 -0300 Subject: [PATCH 015/114] Using controled component --- app/react/Pages/components/PageCreator.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/react/Pages/components/PageCreator.js b/app/react/Pages/components/PageCreator.js index 34c5215d73..48765d7ef9 100644 --- a/app/react/Pages/components/PageCreator.js +++ b/app/react/Pages/components/PageCreator.js @@ -1,4 +1,4 @@ -import { Form, Field } from 'react-redux-form'; +import { Form, Field, Control } from 'react-redux-form'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; @@ -50,9 +50,13 @@ export class PageCreator extends Component {
Enable this page to be used as an entity view page: - - {}} /> - +
From 666f0baac4fc7955851daf324f5b77e6e0ac2565 Mon Sep 17 00:00:00 2001 From: Santiago Date: Fri, 23 Apr 2021 16:20:46 -0300 Subject: [PATCH 016/114] Migrated PageCreator with test to TS --- .../{PageCreator.js => PageCreator.tsx} | 38 +++++++++++------- ...geCreator.spec.js => PageCreator.spec.tsx} | 39 ++++++++++++------- 2 files changed, 50 insertions(+), 27 deletions(-) rename app/react/Pages/components/{PageCreator.js => PageCreator.tsx} (89%) rename app/react/Pages/components/specs/{PageCreator.spec.js => PageCreator.spec.tsx} (73%) diff --git a/app/react/Pages/components/PageCreator.js b/app/react/Pages/components/PageCreator.tsx similarity index 89% rename from app/react/Pages/components/PageCreator.js rename to app/react/Pages/components/PageCreator.tsx index 48765d7ef9..354c08b2ac 100644 --- a/app/react/Pages/components/PageCreator.js +++ b/app/react/Pages/components/PageCreator.tsx @@ -1,7 +1,6 @@ import { Form, Field, Control } from 'react-redux-form'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { MarkDown } from 'app/ReactReduxForms'; @@ -16,7 +15,26 @@ import { Icon, ToggleButton } from 'UI'; import { Translate } from 'app/I18N'; import validator from './ValidatePage'; -export class PageCreator extends Component { +export interface PageCreatorProps { + resetPage: () => {}; + savePage: () => {}; + savingPage: boolean; + formState: { [key: string]: { [key: string]: any } }; + page: { + data: { + _id: string; + title: string; + metadata: { + content?: string; + }; + language: string; + sharedId: string; + entityView: boolean; + }; + }; +} + +export class PageCreator extends Component { componentWillUnmount() { const { resetPage } = this.props; resetPage(); @@ -53,9 +71,7 @@ export class PageCreator extends Component {
@@ -99,7 +115,9 @@ export class PageCreator extends Component {
- Page Javascript + + Page Javascript +
@@ -137,14 +155,6 @@ export class PageCreator extends Component { } } -PageCreator.propTypes = { - resetPage: PropTypes.func, - savePage: PropTypes.func, - page: PropTypes.object, - formState: PropTypes.object, - savingPage: PropTypes.bool, -}; - function mapStateToProps({ page }) { return { page, formState: page.formState, savingPage: page.uiState.get('savingPage') }; } diff --git a/app/react/Pages/components/specs/PageCreator.spec.js b/app/react/Pages/components/specs/PageCreator.spec.tsx similarity index 73% rename from app/react/Pages/components/specs/PageCreator.spec.js rename to app/react/Pages/components/specs/PageCreator.spec.tsx index 2ec84b603b..253bcf7fa3 100644 --- a/app/react/Pages/components/specs/PageCreator.spec.js +++ b/app/react/Pages/components/specs/PageCreator.spec.tsx @@ -1,25 +1,43 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; -import { Form, Field } from 'react-redux-form'; +import { Form, Field, Control } from 'react-redux-form'; import { MarkDown } from 'app/ReactReduxForms'; -import { PageCreator } from '../PageCreator'; +import { PageCreator, PageCreatorProps } from '../PageCreator'; describe('PageCreator', () => { - let component; - let props; + let component: ShallowWrapper; + let props: PageCreatorProps; beforeEach(() => { props = { - page: { data: { title: 'Page title', metadata: {} } }, + page: { + data: { + _id: '', + title: 'Page title', + metadata: {}, + language: 'en', + sharedId: '', + entityView: false, + }, + }, formState: { title: {}, $form: { errors: {} } }, savePage: jasmine.createSpy('savePage'), resetPage: jasmine.createSpy('deletePage'), + savingPage: false, }; }); const render = () => { - component = shallow(); + component = shallow( + + ); }; describe('render', () => { @@ -65,12 +83,7 @@ describe('PageCreator', () => { .parent() .props().model ).toBe('.metadata.script'); - expect( - component - .find('ToggleButton') - .parent() - .props().model - ).toBe('.entityView'); + expect(component.find(Control).props().model).toBe('.entityView'); }); describe('when Title is invalid', () => { From b1f7e083b7eac368eaa6eed606a30c9339fe173b Mon Sep 17 00:00:00 2001 From: Santiago Date: Fri, 23 Apr 2021 17:16:40 -0300 Subject: [PATCH 017/114] Fix type erros --- app/react/Pages/components/PageCreator.tsx | 4 ++-- app/react/Pages/components/specs/PageCreator.spec.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/react/Pages/components/PageCreator.tsx b/app/react/Pages/components/PageCreator.tsx index 354c08b2ac..19faaf7748 100644 --- a/app/react/Pages/components/PageCreator.tsx +++ b/app/react/Pages/components/PageCreator.tsx @@ -1,5 +1,5 @@ import { Form, Field, Control } from 'react-redux-form'; -import { bindActionCreators } from 'redux'; +import { bindActionCreators, Dispatch } from 'redux'; import { connect } from 'react-redux'; import React, { Component } from 'react'; @@ -159,7 +159,7 @@ function mapStateToProps({ page }) { return { page, formState: page.formState, savingPage: page.uiState.get('savingPage') }; } -function mapDispatchToProps(dispatch) { +function mapDispatchToProps(dispatch: Dispatch) { return bindActionCreators({ resetPage: resetPageAction, savePage: savePageAction }, dispatch); } diff --git a/app/react/Pages/components/specs/PageCreator.spec.tsx b/app/react/Pages/components/specs/PageCreator.spec.tsx index 253bcf7fa3..e05c115454 100644 --- a/app/react/Pages/components/specs/PageCreator.spec.tsx +++ b/app/react/Pages/components/specs/PageCreator.spec.tsx @@ -105,7 +105,7 @@ describe('PageCreator', () => { describe('on componentWillUnmount', () => { beforeEach(() => { render(); - component.instance().componentWillUnmount(); + component.unmount(); }); it('should reset the page', () => { From 3ec64f1eb6710e9411c8bcc4a7e0ffb6c02227b0 Mon Sep 17 00:00:00 2001 From: Santiago Date: Sun, 25 Apr 2021 20:01:05 -0300 Subject: [PATCH 018/114] Test for new page action --- app/react/Pages/actions/specs/pageActions.spec.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/react/Pages/actions/specs/pageActions.spec.js b/app/react/Pages/actions/specs/pageActions.spec.js index 3a7cfb58e6..0b312628fc 100644 --- a/app/react/Pages/actions/specs/pageActions.spec.js +++ b/app/react/Pages/actions/specs/pageActions.spec.js @@ -22,6 +22,7 @@ describe('Page actions', () => { spyOn(api, 'delete').and.returnValue(Promise.resolve()); spyOn(formActions, 'reset').and.returnValue('PAGE DATA RESET'); spyOn(formActions, 'merge').and.returnValue('PAGE DATA MERGED'); + spyOn(formActions, 'change').and.returnValue('MODEL VALUE UPDATED'); spyOn(basicActions, 'remove').and.returnValue('PAGE REMOVED'); spyOn(notificationActions, 'notify').and.returnValue('NOTIFIED'); spyOn(browserHistory, 'push'); @@ -112,4 +113,12 @@ describe('Page actions', () => { }); }); }); + + describe('updateValue', () => { + it('should update the model with the provided value', () => { + actions.updateValue('.modelName', 'someValue')(dispatch); + expect(formActions.change).toHaveBeenCalledWith('page.data.modelName', 'someValue'); + expect(dispatch).toHaveBeenCalledWith('MODEL VALUE UPDATED'); + }); + }); }); From e522721bf9cd6775a112bbbb0f0987cb1d85fefc Mon Sep 17 00:00:00 2001 From: Santiago Date: Sun, 25 Apr 2021 20:01:15 -0300 Subject: [PATCH 019/114] Action to update toggle button --- app/react/Pages/actions/pageActions.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/react/Pages/actions/pageActions.js b/app/react/Pages/actions/pageActions.js index 15c04e61cf..217c7dec29 100644 --- a/app/react/Pages/actions/pageActions.js +++ b/app/react/Pages/actions/pageActions.js @@ -14,6 +14,12 @@ export function resetPage() { }; } +export function updateValue(model, value) { + return dispatch => { + dispatch(formActions.change(`page.data${model}`, value)); + }; +} + export function savePage(data) { return dispatch => { dispatch({ type: types.SAVING_PAGE }); From ccbe60dcc369daeea1af90cf9019bbb556e239b9 Mon Sep 17 00:00:00 2001 From: Santiago Date: Sun, 25 Apr 2021 20:06:00 -0300 Subject: [PATCH 020/114] Updated pageCreator test props with new action --- app/react/Pages/components/specs/PageCreator.spec.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/react/Pages/components/specs/PageCreator.spec.tsx b/app/react/Pages/components/specs/PageCreator.spec.tsx index e05c115454..f99a04e65f 100644 --- a/app/react/Pages/components/specs/PageCreator.spec.tsx +++ b/app/react/Pages/components/specs/PageCreator.spec.tsx @@ -24,6 +24,7 @@ describe('PageCreator', () => { formState: { title: {}, $form: { errors: {} } }, savePage: jasmine.createSpy('savePage'), resetPage: jasmine.createSpy('deletePage'), + updateValue: jasmine.createSpy('updateValue'), savingPage: false, }; }); @@ -35,6 +36,7 @@ describe('PageCreator', () => { formState={props.formState} savePage={props.savePage} resetPage={props.resetPage} + updateValue={props.updateValue} savingPage={props.savingPage} /> ); From 491b8e8325c2c62c25df1e8140d5803cb886e288 Mon Sep 17 00:00:00 2001 From: Santiago Date: Sun, 25 Apr 2021 20:06:31 -0300 Subject: [PATCH 021/114] Added action to pageCreator to toggle entity view settng --- app/react/Pages/components/PageCreator.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/react/Pages/components/PageCreator.tsx b/app/react/Pages/components/PageCreator.tsx index 19faaf7748..c84d4242d2 100644 --- a/app/react/Pages/components/PageCreator.tsx +++ b/app/react/Pages/components/PageCreator.tsx @@ -7,6 +7,7 @@ import { MarkDown } from 'app/ReactReduxForms'; import { resetPage as resetPageAction, savePage as savePageAction, + updateValue as updateValueAction, } from 'app/Pages/actions/pageActions'; import ShowIf from 'app/App/ShowIf'; import { BackButton } from 'app/Layout'; @@ -18,6 +19,7 @@ import validator from './ValidatePage'; export interface PageCreatorProps { resetPage: () => {}; savePage: () => {}; + updateValue: (model: string, value: any) => {}; savingPage: boolean; formState: { [key: string]: { [key: string]: any } }; page: { @@ -41,7 +43,7 @@ export class PageCreator extends Component { } render() { - const { formState, page, savePage, savingPage } = this.props; + const { formState, page, savePage, updateValue, savingPage } = this.props; const backUrl = '/settings/pages'; const pageUrl = `/page/${page.data.sharedId}`; @@ -71,7 +73,10 @@ export class PageCreator extends Component { { + updateValue('.entityView', !page.data.entityView); + }} />
@@ -160,7 +165,10 @@ function mapStateToProps({ page }) { } function mapDispatchToProps(dispatch: Dispatch) { - return bindActionCreators({ resetPage: resetPageAction, savePage: savePageAction }, dispatch); + return bindActionCreators( + { resetPage: resetPageAction, savePage: savePageAction, updateValue: updateValueAction }, + dispatch + ); } export default connect(mapStateToProps, mapDispatchToProps)(PageCreator); From 8a8204cc180ea06a9ee1ec339d8b1502cdb6eaf0 Mon Sep 17 00:00:00 2001 From: Santiago Date: Sun, 25 Apr 2021 20:30:03 -0300 Subject: [PATCH 022/114] pageActions test to TS --- .../specs/{pageActions.spec.js => pageActions.spec.ts} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename app/react/Pages/actions/specs/{pageActions.spec.js => pageActions.spec.ts} (97%) diff --git a/app/react/Pages/actions/specs/pageActions.spec.js b/app/react/Pages/actions/specs/pageActions.spec.ts similarity index 97% rename from app/react/Pages/actions/specs/pageActions.spec.js rename to app/react/Pages/actions/specs/pageActions.spec.ts index 0b312628fc..1e8b806472 100644 --- a/app/react/Pages/actions/specs/pageActions.spec.js +++ b/app/react/Pages/actions/specs/pageActions.spec.ts @@ -12,7 +12,7 @@ import api from 'app/Pages/PagesAPI'; import * as actions from '../pageActions'; describe('Page actions', () => { - let dispatch; + let dispatch: jasmine.Spy; beforeEach(() => { dispatch = jasmine.createSpy('dispatch'); @@ -80,7 +80,7 @@ describe('Page actions', () => { }); describe('on error', () => { it('should dispatch page saved', done => { - api.save.and.callFake(() => Promise.reject(new Error())); + api.save.and.callFake(async () => Promise.reject(new Error())); actions .savePage('data')(dispatch) .then(() => { From 3bd9d1110d71ca9e1387cca4758b9532db692ae6 Mon Sep 17 00:00:00 2001 From: Santiago Date: Mon, 26 Apr 2021 12:56:19 -0300 Subject: [PATCH 023/114] Type fixes --- app/react/Pages/EditPage.js | 2 +- app/react/Pages/NewPage.js | 2 +- .../Pages/actions/specs/pageActions.spec.ts | 10 ++-- app/react/Pages/components/PageCreator.tsx | 53 +++++++------------ .../components/specs/PageCreator.spec.tsx | 25 ++++----- app/react/istore.d.ts | 6 +++ 6 files changed, 45 insertions(+), 53 deletions(-) diff --git a/app/react/Pages/EditPage.js b/app/react/Pages/EditPage.js index ec9f5419ec..17b5468a4f 100644 --- a/app/react/Pages/EditPage.js +++ b/app/react/Pages/EditPage.js @@ -3,7 +3,7 @@ import { actions as formActions } from 'react-redux-form'; import RouteHandler from 'app/App/RouteHandler'; -import PageCreator from 'app/Pages/components/PageCreator'; +import { PageCreator } from 'app/Pages/components/PageCreator'; import pagesAPI from './PagesAPI'; export default class EditPage extends RouteHandler { diff --git a/app/react/Pages/NewPage.js b/app/react/Pages/NewPage.js index d883742974..e55b69eaf9 100644 --- a/app/react/Pages/NewPage.js +++ b/app/react/Pages/NewPage.js @@ -1,5 +1,5 @@ import React from 'react'; -import PageCreator from 'app/Pages/components/PageCreator'; +import { PageCreator } from 'app/Pages/components/PageCreator'; import RouteHandler from 'app/App/RouteHandler'; export default class NewPage extends RouteHandler { diff --git a/app/react/Pages/actions/specs/pageActions.spec.ts b/app/react/Pages/actions/specs/pageActions.spec.ts index 1e8b806472..4517d23807 100644 --- a/app/react/Pages/actions/specs/pageActions.spec.ts +++ b/app/react/Pages/actions/specs/pageActions.spec.ts @@ -13,12 +13,14 @@ import * as actions from '../pageActions'; describe('Page actions', () => { let dispatch: jasmine.Spy; + let apiSave: jasmine.Spy; beforeEach(() => { dispatch = jasmine.createSpy('dispatch'); - spyOn(api, 'save').and.returnValue( - Promise.resolve({ _id: 'newId', sharedId: 'newSharedId', _rev: 'newRev' }) - ); + apiSave = jasmine + .createSpy() + .and.returnValue(Promise.resolve({ _id: 'newId', sharedId: 'newSharedId', _rev: 'newRev' })); + api.save = apiSave; spyOn(api, 'delete').and.returnValue(Promise.resolve()); spyOn(formActions, 'reset').and.returnValue('PAGE DATA RESET'); spyOn(formActions, 'merge').and.returnValue('PAGE DATA MERGED'); @@ -80,7 +82,7 @@ describe('Page actions', () => { }); describe('on error', () => { it('should dispatch page saved', done => { - api.save.and.callFake(async () => Promise.reject(new Error())); + apiSave.and.callFake(async () => Promise.reject(new Error())); actions .savePage('data')(dispatch) .then(() => { diff --git a/app/react/Pages/components/PageCreator.tsx b/app/react/Pages/components/PageCreator.tsx index c84d4242d2..ad121ef093 100644 --- a/app/react/Pages/components/PageCreator.tsx +++ b/app/react/Pages/components/PageCreator.tsx @@ -1,6 +1,6 @@ import { Form, Field, Control } from 'react-redux-form'; import { bindActionCreators, Dispatch } from 'redux'; -import { connect } from 'react-redux'; +import { connect, ConnectedProps } from 'react-redux'; import React, { Component } from 'react'; import { MarkDown } from 'app/ReactReduxForms'; @@ -13,30 +13,27 @@ import ShowIf from 'app/App/ShowIf'; import { BackButton } from 'app/Layout'; import { Icon, ToggleButton } from 'UI'; +import { IStore } from 'app/istore'; import { Translate } from 'app/I18N'; import validator from './ValidatePage'; -export interface PageCreatorProps { - resetPage: () => {}; - savePage: () => {}; - updateValue: (model: string, value: any) => {}; - savingPage: boolean; - formState: { [key: string]: { [key: string]: any } }; - page: { - data: { - _id: string; - title: string; - metadata: { - content?: string; - }; - language: string; - sharedId: string; - entityView: boolean; - }; - }; -} +const mapStateToProps = ({ page }: IStore) => ({ + page, + formState: page.formState, + savingPage: page.uiState.get('savingPage'), +}); + +const mapDispatchToProps = (dispatch: Dispatch<{}>) => + bindActionCreators( + { resetPage: resetPageAction, savePage: savePageAction, updateValue: updateValueAction }, + dispatch + ); + +const connector = connect(mapStateToProps, mapDispatchToProps); + +export type mappedProps = ConnectedProps; -export class PageCreator extends Component { +class PageCreator extends Component { componentWillUnmount() { const { resetPage } = this.props; resetPage(); @@ -160,15 +157,5 @@ export class PageCreator extends Component { } } -function mapStateToProps({ page }) { - return { page, formState: page.formState, savingPage: page.uiState.get('savingPage') }; -} - -function mapDispatchToProps(dispatch: Dispatch) { - return bindActionCreators( - { resetPage: resetPageAction, savePage: savePageAction, updateValue: updateValueAction }, - dispatch - ); -} - -export default connect(mapStateToProps, mapDispatchToProps)(PageCreator); +const container = connector(PageCreator); +export { container as PageCreator }; diff --git a/app/react/Pages/components/specs/PageCreator.spec.tsx b/app/react/Pages/components/specs/PageCreator.spec.tsx index f99a04e65f..a1ffa3c2f7 100644 --- a/app/react/Pages/components/specs/PageCreator.spec.tsx +++ b/app/react/Pages/components/specs/PageCreator.spec.tsx @@ -1,15 +1,18 @@ import React from 'react'; +import Immutable from 'immutable'; import { shallow, ShallowWrapper } from 'enzyme'; import { Form, Field, Control } from 'react-redux-form'; import { MarkDown } from 'app/ReactReduxForms'; -import { PageCreator, PageCreatorProps } from '../PageCreator'; +import { PageCreator, mappedProps } from '../PageCreator'; describe('PageCreator', () => { - let component: ShallowWrapper; - let props: PageCreatorProps; + let component: ShallowWrapper; + let props: mappedProps; beforeEach(() => { + const formState = { title: {}, $form: { errors: {} } }; + const uiState = Immutable.fromJS({ savingPage: false }); props = { page: { data: { @@ -20,8 +23,10 @@ describe('PageCreator', () => { sharedId: '', entityView: false, }, + formState, + uiState, }, - formState: { title: {}, $form: { errors: {} } }, + formState, savePage: jasmine.createSpy('savePage'), resetPage: jasmine.createSpy('deletePage'), updateValue: jasmine.createSpy('updateValue'), @@ -30,16 +35,8 @@ describe('PageCreator', () => { }); const render = () => { - component = shallow( - - ); + // eslint-disable-next-line react/jsx-props-no-spreading + component = shallow(); }; describe('render', () => { diff --git a/app/react/istore.d.ts b/app/react/istore.d.ts index 5ee62a101f..5f81ba83c5 100644 --- a/app/react/istore.d.ts +++ b/app/react/istore.d.ts @@ -10,6 +10,7 @@ import { UserGroupSchema } from 'shared/types/userGroupType'; import { ConnectionSchema } from 'shared/types/connectionType'; import { Settings } from 'shared/types/settingsType'; import { FileType } from 'shared/types/fileType'; +import { PageType } from 'shared/types/pageType'; export interface TasksState { SyncState?: TaskStatus; @@ -125,4 +126,9 @@ export interface IStore { collection: IImmutable; }; userGroups: IImmutable; + page: { + data: PageType; + uiState: IImmutable<{ savingPage: boolean }>; + formState: any; + }; } From 9d0dfadd57ef6add4706d2bbc88da71d7de389ac Mon Sep 17 00:00:00 2001 From: RafaPolit Date: Mon, 26 Apr 2021 12:50:47 -0500 Subject: [PATCH 024/114] Added filter type. --- app/shared/types/Immutable.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/shared/types/Immutable.ts b/app/shared/types/Immutable.ts index 80e2e210e9..d3281ee8a8 100644 --- a/app/shared/types/Immutable.ts +++ b/app/shared/types/Immutable.ts @@ -15,4 +15,7 @@ export type IImmutable = T extends string : { toJS(): T; get(_field: Field): IImmutable; + filter( + fn: (listElement: IImmutable) => boolean | undefined + ): Immutable.List>; }; From aded7d272ecc03cc6d68c5d757409b011c435e54 Mon Sep 17 00:00:00 2001 From: RafaPolit Date: Mon, 26 Apr 2021 12:50:56 -0500 Subject: [PATCH 025/114] Incomplete test --- .../components/specs/ViewTemplateAsPage.spec.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx b/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx index df85cd6d58..9b5664108d 100644 --- a/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx +++ b/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx @@ -1,12 +1,21 @@ import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; +import Immutable from 'immutable'; import { ViewTemplateAsPage } from '../ViewTemplateAsPage'; +import { IStore } from 'app/istore'; describe('ViewTemplateAsPage', () => { let component: ShallowWrapper; + let state: IStore; - it('should contain a label with an info icon and a ToggleChildren component', () => { + beforeEach(() => { + state = { + pages: Immutable.fromJS({}), + }; + }); + + it('should contain a label with a tip and a toggled pages that can be used', () => { component = shallow(); expect(component.find('label')).toHaveLength(1); expect(component.find('Tip')).toHaveLength(1); From dddf1ff4d423f92264099033c0173c1eaa952f7c Mon Sep 17 00:00:00 2001 From: RafaPolit Date: Mon, 26 Apr 2021 12:51:15 -0500 Subject: [PATCH 026/114] Added loadPages to page selector in templates --- app/react/Pages/actions/pageActions.js | 8 +++ .../Templates/components/MetadataTemplate.tsx | 30 ++++---- .../components/ViewTemplateAsPage.tsx | 68 +++++++++++++------ app/react/istore.d.ts | 1 + 4 files changed, 72 insertions(+), 35 deletions(-) diff --git a/app/react/Pages/actions/pageActions.js b/app/react/Pages/actions/pageActions.js index 217c7dec29..3e4737a927 100644 --- a/app/react/Pages/actions/pageActions.js +++ b/app/react/Pages/actions/pageActions.js @@ -7,6 +7,14 @@ import { notificationActions } from 'app/Notifications'; import api from 'app/Pages/PagesAPI'; import * as types from 'app/Pages/actions/actionTypes'; +// TEST!!! +export function loadPages() { + return async dispatch => { + const pages = await api.get(new RequestParams()); + dispatch(actions.set('pages', pages)); + }; +} + export function resetPage() { return dispatch => { dispatch(formActions.reset('page.data')); diff --git a/app/react/Templates/components/MetadataTemplate.tsx b/app/react/Templates/components/MetadataTemplate.tsx index b4fe9786cc..d78cc0df91 100644 --- a/app/react/Templates/components/MetadataTemplate.tsx +++ b/app/react/Templates/components/MetadataTemplate.tsx @@ -1,4 +1,15 @@ +import React, { Component } from 'react'; +import { DropTarget } from 'react-dnd'; +import { List } from 'immutable'; +import PropTypes from 'prop-types'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { actions as formActions, Control, Field, Form } from 'react-redux-form'; +import { Icon } from 'UI'; + import { TemplateSchema } from 'shared/types/templateType'; +import { PropertySchema } from 'shared/types/commonTypes'; + import ShowIf from 'app/App/ShowIf'; import { FormGroup } from 'app/Forms'; import ColorPicker from 'app/Forms/components/ColorPicker'; @@ -14,18 +25,9 @@ import { import MetadataProperty from 'app/Templates/components/MetadataProperty'; import RemovePropertyConfirm from 'app/Templates/components/RemovePropertyConfirm'; import { COLORS } from 'app/utils/colors'; -import { List } from 'immutable'; -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { DropTarget } from 'react-dnd'; -import { connect } from 'react-redux'; -import { actions as formActions, Control, Field, Form } from 'react-redux-form'; -import { bindActionCreators } from 'redux'; -import { PropertySchema } from 'shared/types/commonTypes'; -import { Icon } from 'UI'; import { ViewTemplateAsPage } from './ViewTemplateAsPage'; -import validator from './ValidateTemplate'; // eslint-disable-line import/no-named-as-default, import/no-named-as-default-member +import validator from './ValidateTemplate'; interface MetadataTemplateProps { notify(message: any, type: any): any; @@ -41,9 +43,8 @@ interface MetadataTemplateProps { _id?: string; } -const getTemplateDefaultColor = (allTemplates: List, template: any) => { - return template.data.color ? template.data.color : COLORS[allTemplates.size % COLORS.length]; -}; +const getTemplateDefaultColor = (allTemplates: List, template: any) => + template.data.color ? template.data.color : COLORS[allTemplates.size % COLORS.length]; export class MetadataTemplate extends Component { static propTypes: any; @@ -54,7 +55,8 @@ export class MetadataTemplate extends Component { static defaultProps: MetadataTemplateProps = { notify, - saveTemplate, // eslint-disable-line react/default-props-match-prop-types + // eslint-disable-next-line react/default-props-match-prop-types + saveTemplate, savingTemplate: false, defaultColor: null, properties: [], diff --git a/app/react/Templates/components/ViewTemplateAsPage.tsx b/app/react/Templates/components/ViewTemplateAsPage.tsx index c36a14a1c8..0a8b69fe0c 100644 --- a/app/react/Templates/components/ViewTemplateAsPage.tsx +++ b/app/react/Templates/components/ViewTemplateAsPage.tsx @@ -1,26 +1,52 @@ -import React from 'react'; +import React, { useEffect } from 'react'; +import { Dispatch, bindActionCreators } from 'redux'; +import { connect, ConnectedProps } from 'react-redux'; +import { IStore } from 'app/istore'; import { Tip } from 'app/Layout'; - import { ToggleChildren } from 'app/Settings/components/ToggleChildren'; import { t } from 'app/I18N'; +import { loadPages as loadPagesAction } from 'app/Pages/actions/pageActions'; + +const mapStateToProps = ({ pages }: IStore) => ({ + pages: pages.filter(p => p.get('entityView')), +}); + +const mapDispatchToProps = (dispatch: Dispatch<{}>) => + bindActionCreators({ loadPages: loadPagesAction }, dispatch); + +const connector = connect(mapStateToProps, mapDispatchToProps); + +type mappedProps = ConnectedProps; + +const ViewTemplateAsPage = ({ pages, loadPages }: mappedProps) => { + useEffect(() => { + loadPages(); + }, []); + + return ( +
+ + + + +
+ ); +}; -export const ViewTemplateAsPage = () => ( -
- - - - -
-); +const container = connector(ViewTemplateAsPage); +export { container as ViewTemplateAsPage }; diff --git a/app/react/istore.d.ts b/app/react/istore.d.ts index 5f81ba83c5..e357adbd80 100644 --- a/app/react/istore.d.ts +++ b/app/react/istore.d.ts @@ -131,4 +131,5 @@ export interface IStore { uiState: IImmutable<{ savingPage: boolean }>; formState: any; }; + pages: IImmutable; } From 4a068d32893f015794308e5e3900282b135f0782 Mon Sep 17 00:00:00 2001 From: Santiago Date: Mon, 26 Apr 2021 17:10:46 -0300 Subject: [PATCH 027/114] Adapted viewTemplateAsPage test --- .../specs/ViewTemplateAsPage.spec.tsx | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx b/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx index 9b5664108d..14a22f2fc5 100644 --- a/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx +++ b/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx @@ -1,24 +1,36 @@ import React from 'react'; -import { shallow, ShallowWrapper } from 'enzyme'; import Immutable from 'immutable'; +import thunk from 'redux-thunk'; + +import configureStore, { MockStore, MockStoreCreator } from 'redux-mock-store'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { Provider } from 'react-redux'; import { ViewTemplateAsPage } from '../ViewTemplateAsPage'; -import { IStore } from 'app/istore'; -describe('ViewTemplateAsPage', () => { - let component: ShallowWrapper; - let state: IStore; +const middlewares = [thunk]; - beforeEach(() => { - state = { - pages: Immutable.fromJS({}), - }; +describe('ViewTemplateAsPage', () => { + const mockStoreCreator: MockStoreCreator = configureStore(middlewares); + const store: MockStore = mockStoreCreator({ + pages: Immutable.fromJS([ + { title: 'page 1', _id: 'abc123', entityView: true }, + { title: 'page 2', _id: 'def345', entityView: false }, + { title: 'page 3', _id: 'df3485' }, + ]), }); + const component: ShallowWrapper = shallow( + + + + ) + .dive() + .dive(); it('should contain a label with a tip and a toggled pages that can be used', () => { - component = shallow(); expect(component.find('label')).toHaveLength(1); expect(component.find('Tip')).toHaveLength(1); expect(component.find('ToggleChildren')).toHaveLength(1); + expect(component.find('option').props().children).toBe('page 1'); }); }); From 49385114bca2ae62ca1a5a5e4cc520bcee754055 Mon Sep 17 00:00:00 2001 From: Santiago Date: Mon, 26 Apr 2021 17:55:32 -0300 Subject: [PATCH 028/114] Added check in case no page is set for template view --- .../components/ViewTemplateAsPage.tsx | 26 +++++----- .../specs/ViewTemplateAsPage.spec.tsx | 48 +++++++++++++------ 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/app/react/Templates/components/ViewTemplateAsPage.tsx b/app/react/Templates/components/ViewTemplateAsPage.tsx index 0a8b69fe0c..38d164e840 100644 --- a/app/react/Templates/components/ViewTemplateAsPage.tsx +++ b/app/react/Templates/components/ViewTemplateAsPage.tsx @@ -27,23 +27,27 @@ const ViewTemplateAsPage = ({ pages, loadPages }: mappedProps) => { return (
- - - + {pages.size > 0 && ( + + + + )}
); }; diff --git a/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx b/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx index 14a22f2fc5..03fd085726 100644 --- a/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx +++ b/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx @@ -12,25 +12,43 @@ const middlewares = [thunk]; describe('ViewTemplateAsPage', () => { const mockStoreCreator: MockStoreCreator = configureStore(middlewares); - const store: MockStore = mockStoreCreator({ - pages: Immutable.fromJS([ - { title: 'page 1', _id: 'abc123', entityView: true }, - { title: 'page 2', _id: 'def345', entityView: false }, - { title: 'page 3', _id: 'df3485' }, - ]), - }); - const component: ShallowWrapper = shallow( - - - - ) - .dive() - .dive(); + let component: ShallowWrapper; + let store: MockStore; + const render = () => { + component = shallow( + + + + ) + .dive() + .dive(); + }; - it('should contain a label with a tip and a toggled pages that can be used', () => { + it('should contain a label with a tip, and toggled pages that can be used', () => { + store = mockStoreCreator({ + pages: Immutable.fromJS([ + { title: 'page 1', _id: 'abc123', entityView: true }, + { title: 'page 2', _id: 'def345', entityView: false }, + { title: 'page 3', _id: 'df3485' }, + ]), + }); + render(); expect(component.find('label')).toHaveLength(1); expect(component.find('Tip')).toHaveLength(1); expect(component.find('ToggleChildren')).toHaveLength(1); expect(component.find('option').props().children).toBe('page 1'); }); + + it('should display a message saying that no pages are available if none are present', () => { + store = mockStoreCreator({ + pages: Immutable.fromJS([ + { title: 'page 2', _id: 'def345', entityView: false }, + { title: 'page 3', _id: 'df3485' }, + ]), + }); + render(); + expect(component.find('label').props().children).toContain( + 'There are no pages enabled for entity view' + ); + }); }); From 42108ab01f98f19acf4e052ab2e0bbf23658348c Mon Sep 17 00:00:00 2001 From: Santiago Date: Mon, 26 Apr 2021 18:42:28 -0300 Subject: [PATCH 029/114] Logic for no pages --- app/react/Templates/components/ViewTemplateAsPage.tsx | 2 +- .../components/specs/ViewTemplateAsPage.spec.tsx | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/react/Templates/components/ViewTemplateAsPage.tsx b/app/react/Templates/components/ViewTemplateAsPage.tsx index 38d164e840..17d1ab9217 100644 --- a/app/react/Templates/components/ViewTemplateAsPage.tsx +++ b/app/react/Templates/components/ViewTemplateAsPage.tsx @@ -9,7 +9,7 @@ import { t } from 'app/I18N'; import { loadPages as loadPagesAction } from 'app/Pages/actions/pageActions'; const mapStateToProps = ({ pages }: IStore) => ({ - pages: pages.filter(p => p.get('entityView')), + pages: pages?.filter(p => p.get('entityView')) || [], }); const mapDispatchToProps = (dispatch: Dispatch<{}>) => diff --git a/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx b/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx index 03fd085726..42bb649779 100644 --- a/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx +++ b/app/react/Templates/components/specs/ViewTemplateAsPage.spec.tsx @@ -51,4 +51,14 @@ describe('ViewTemplateAsPage', () => { 'There are no pages enabled for entity view' ); }); + + it('should not throw error when there are no pages', () => { + store = mockStoreCreator({ + pages: Immutable.fromJS(undefined), + }); + render(); + expect(component.find('label').props().children).toContain( + 'There are no pages enabled for entity view' + ); + }); }); From 3ca339a80a010bef8692f04daa63025e1811d956 Mon Sep 17 00:00:00 2001 From: Santiago Date: Mon, 26 Apr 2021 18:59:39 -0300 Subject: [PATCH 030/114] Minor test error fix --- app/react/Pages/specs/EditPage.spec.js | 2 +- app/react/Pages/specs/NewPage.spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/react/Pages/specs/EditPage.spec.js b/app/react/Pages/specs/EditPage.spec.js index 7261e62c21..77b3f0b41a 100644 --- a/app/react/Pages/specs/EditPage.spec.js +++ b/app/react/Pages/specs/EditPage.spec.js @@ -2,7 +2,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import PagesAPI from 'app/Pages/PagesAPI'; -import PageCreator from 'app/Pages/components/PageCreator'; +import { PageCreator } from 'app/Pages/components/PageCreator'; import RouteHandler from 'app/App/RouteHandler'; import EditPage from '../EditPage'; diff --git a/app/react/Pages/specs/NewPage.spec.js b/app/react/Pages/specs/NewPage.spec.js index 2b85e39f93..ff86e08039 100644 --- a/app/react/Pages/specs/NewPage.spec.js +++ b/app/react/Pages/specs/NewPage.spec.js @@ -1,6 +1,6 @@ import React from 'react'; import { shallow } from 'enzyme'; -import PageCreator from 'app/Pages/components/PageCreator'; +import { PageCreator } from 'app/Pages/components/PageCreator'; import NewPage from '../NewPage'; describe('NewPage', () => { From 9c36c7b87ee43f388d0caebde53873c4df3cb471 Mon Sep 17 00:00:00 2001 From: Santiago Date: Mon, 26 Apr 2021 19:54:37 -0300 Subject: [PATCH 031/114] pageActions to TS --- .../{pageActions.js => pageActions.ts} | 23 +++++++++++-------- .../Pages/actions/specs/pageActions.spec.ts | 10 ++++---- 2 files changed, 18 insertions(+), 15 deletions(-) rename app/react/Pages/actions/{pageActions.js => pageActions.ts} (75%) diff --git a/app/react/Pages/actions/pageActions.js b/app/react/Pages/actions/pageActions.ts similarity index 75% rename from app/react/Pages/actions/pageActions.js rename to app/react/Pages/actions/pageActions.ts index 3e4737a927..336d46e5ee 100644 --- a/app/react/Pages/actions/pageActions.js +++ b/app/react/Pages/actions/pageActions.ts @@ -1,39 +1,42 @@ import { browserHistory } from 'react-router'; +import { Dispatch } from 'redux'; import { actions as formActions } from 'react-redux-form'; -import { RequestParams } from 'app/utils/RequestParams'; import { actions } from 'app/BasicReducer'; +import { RequestParams } from 'app/utils/RequestParams'; import { notificationActions } from 'app/Notifications'; import api from 'app/Pages/PagesAPI'; import * as types from 'app/Pages/actions/actionTypes'; +import { PageType } from 'shared/types/pageType'; + // TEST!!! export function loadPages() { - return async dispatch => { + return async (dispatch: Dispatch<{}>) => { const pages = await api.get(new RequestParams()); dispatch(actions.set('pages', pages)); }; } export function resetPage() { - return dispatch => { + return (dispatch: Dispatch<{}>) => { dispatch(formActions.reset('page.data')); dispatch(formActions.setInitial('page.data')); }; } -export function updateValue(model, value) { - return dispatch => { +export function updateValue(model: string, value: any) { + return (dispatch: Dispatch<{}>) => { dispatch(formActions.change(`page.data${model}`, value)); }; } -export function savePage(data) { - return dispatch => { +export function savePage(data: PageType) { + return (dispatch: Dispatch<{}>) => { dispatch({ type: types.SAVING_PAGE }); return api .save(new RequestParams(data)) - .then(response => { + .then((response: PageType & { _rev: any }) => { dispatch(notificationActions.notify('Saved successfully.', 'success')); dispatch( formActions.merge('page.data', { @@ -51,8 +54,8 @@ export function savePage(data) { }; } -export function deletePage(page) { - return dispatch => +export function deletePage(page: PageType) { + return (dispatch: Dispatch<{}>) => api.delete(new RequestParams({ sharedId: page.sharedId })).then(() => { dispatch(actions.remove('pages', page)); }); diff --git a/app/react/Pages/actions/specs/pageActions.spec.ts b/app/react/Pages/actions/specs/pageActions.spec.ts index 4517d23807..4cf36ac671 100644 --- a/app/react/Pages/actions/specs/pageActions.spec.ts +++ b/app/react/Pages/actions/specs/pageActions.spec.ts @@ -40,16 +40,16 @@ describe('Page actions', () => { describe('savePage', () => { it('should dispatch a saving page and save the data', () => { - actions.savePage('data')(dispatch); + actions.savePage({ title: 'A title' })(dispatch); expect(dispatch.calls.count()).toBe(1); expect(dispatch).toHaveBeenCalledWith({ type: 'SAVING_PAGE' }); - expect(api.save).toHaveBeenCalledWith(new RequestParams('data')); + expect(api.save).toHaveBeenCalledWith(new RequestParams({ title: 'A title' })); }); describe('upon success', () => { beforeEach(done => { actions - .savePage('data')(dispatch) + .savePage({ title: 'A title' })(dispatch) .then(() => { done(); }); @@ -84,7 +84,7 @@ describe('Page actions', () => { it('should dispatch page saved', done => { apiSave.and.callFake(async () => Promise.reject(new Error())); actions - .savePage('data')(dispatch) + .savePage({ title: 'A title' })(dispatch) .then(() => { expect(dispatch).toHaveBeenCalledWith({ type: 'PAGE_SAVED', data: {} }); done(); @@ -94,7 +94,7 @@ describe('Page actions', () => { }); describe('deletePage', () => { - const data = { sharedId: 'page1', _id: 'id' }; + const data = { sharedId: 'page1', _id: 'id', title: 'A title' }; it('should delete the page', () => { actions.deletePage(data)(dispatch); expect(api.delete).toHaveBeenCalledWith(new RequestParams({ sharedId: 'page1' })); From 5fe102586fdb54b170723dc995ba651ca4d04af3 Mon Sep 17 00:00:00 2001 From: Santiago Date: Tue, 27 Apr 2021 15:28:10 -0300 Subject: [PATCH 032/114] Added value for entity view page on Template model --- app/api/templates/templatesModel.ts | 1 + app/shared/types/templateSchema.ts | 1 + app/shared/types/templateType.d.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/app/api/templates/templatesModel.ts b/app/api/templates/templatesModel.ts index 9d57c4e67b..269f33ca1a 100644 --- a/app/api/templates/templatesModel.ts +++ b/app/api/templates/templatesModel.ts @@ -6,6 +6,7 @@ const mongoSchema = new mongoose.Schema({ name: String, color: { type: String, default: '' }, default: Boolean, + entityViewPage: { type: String, default: '' }, properties: [ new mongoose.Schema({ id: String, diff --git a/app/shared/types/templateSchema.ts b/app/shared/types/templateSchema.ts index 3ac9a98c43..4e0978d097 100644 --- a/app/shared/types/templateSchema.ts +++ b/app/shared/types/templateSchema.ts @@ -250,6 +250,7 @@ export const templateSchema = { name: { type: 'string', minLength: 1 }, color: { type: 'string', default: '' }, default: { type: 'boolean', default: false }, + entityViewPage: { type: 'string', default: '' }, commonProperties: { type: 'array', requireTitleProperty: true, diff --git a/app/shared/types/templateType.d.ts b/app/shared/types/templateType.d.ts index 54651b8285..96d25d4949 100644 --- a/app/shared/types/templateType.d.ts +++ b/app/shared/types/templateType.d.ts @@ -8,6 +8,7 @@ export interface TemplateSchema { name: string; color?: string; default?: boolean; + entityViewPage?: string; commonProperties?: [PropertySchema, ...PropertySchema[]]; properties?: PropertySchema[]; [k: string]: unknown | undefined; From 42c6c438f00a338ddb55fefccf43be0a9fdf9e0a Mon Sep 17 00:00:00 2001 From: Santiago Date: Tue, 27 Apr 2021 15:29:18 -0300 Subject: [PATCH 033/114] Toggled base on entityViewPage value --- .../Templates/components/MetadataTemplate.tsx | 14 ++++++++++++-- .../Templates/components/ViewTemplateAsPage.tsx | 7 ++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/app/react/Templates/components/MetadataTemplate.tsx b/app/react/Templates/components/MetadataTemplate.tsx index d78cc0df91..d721a3ec4d 100644 --- a/app/react/Templates/components/MetadataTemplate.tsx +++ b/app/react/Templates/components/MetadataTemplate.tsx @@ -1,3 +1,4 @@ +/* eslint-disable max-lines */ import React, { Component } from 'react'; import { DropTarget } from 'react-dnd'; import { List } from 'immutable'; @@ -40,6 +41,7 @@ interface MetadataTemplateProps { relationType?: any; savingTemplate: boolean; templates?: any; + entityViewPage?: string; _id?: string; } @@ -133,7 +135,9 @@ export class MetadataTemplate extends Component { /> )} - + + + {connectDropTarget(