Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix sorting in dropdown #187

Merged
merged 9 commits into from
Jun 22, 2022
120 changes: 119 additions & 1 deletion src/backend/controllers/pages.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import Page, { PageData } from '../models/page';
import Alias from '../models/alias';
import PagesOrder from './pagesOrder';
import PageOrder from '../models/pageOrder';
import HttpException from "../exceptions/httpException";

type PageDataFields = keyof PageData;

Expand Down Expand Up @@ -62,14 +65,129 @@ class Pages {
return nullFilteredPages;
}

/**
* Group all pages by their parents
* If the pageId is passed, it excludes passed page from result pages
*
* @param {string} pageId - pageId to exclude from result pages
* @returns {Page[]}
*/
public static async groupByParent(pageId = ''): Promise<Page[]> {
const result: Page[] = [];
const orderGroupedByParent: Record<string, string[]> = {};
const rootPageOrder = await PagesOrder.getRootPageOrder();
const childPageOrder = await PagesOrder.getChildPageOrder();
const orphanPageOrder: PageOrder[] = [];

/**
* If there is no root and child page order, then it returns an empty array
*/
if (!rootPageOrder || (!rootPageOrder && childPageOrder.length <= 0)) {
return [];
}

const pages = (await this.getAll()).reduce((map, _page) => {
map.set(_page._id, _page);

return map;
}, new Map);
const idsOfRootPages = rootPageOrder.order;

/**
* It groups root pages and 1 level pages by its parent
*/
idsOfRootPages.reduce((prev, curr, idx) => {
const childPages:PageOrder[] = [];

childPageOrder.forEach((pageOrder, _idx) => {
if (pageOrder.page === curr) {
childPages.push(pageOrder);
childPageOrder.splice(_idx, 1);
}
});

const hasChildPage = childPages.length > 0;

prev[curr] = [];
prev[curr].push(curr);

/**
* It attaches 1 level page id to its parent page id
*/
if (hasChildPage) {
prev[curr].push(...childPages[0].order);
}

/**
* If non-attached childPages which is not 1 level page still remains,
* It is stored as an orphan page so that it can be processed in the next statements
*/
if (idx === idsOfRootPages.length - 1 && childPageOrder.length > 0) {
orphanPageOrder.push(...childPageOrder);
}

return prev;
}, orderGroupedByParent);

let count = 0;

/**
* It groups remained ungrouped pages by its parent
*/
while (orphanPageOrder.length > 0) {
if (count >= 1000) {
throw new HttpException(500, `Page cannot be processed`);
}

orphanPageOrder.forEach((orphanOrder, idx) => {
// It loops each of grouped orders formatted as [root page id(1): corresponding child pages id(2)]
Object.entries(orderGroupedByParent).forEach(([parentPageId, value]) => {
// If (2) contains orphanOrder's parent id(page)
if (orphanOrder.page && orphanOrder.order && value.includes(orphanOrder.page)) {
neSpecc marked this conversation as resolved.
Show resolved Hide resolved
// Append orphanOrder's id(order) into its parent id
orderGroupedByParent[parentPageId].splice(value.indexOf(orphanOrder.page) + 1, 0, ...orphanOrder.order);
// Finally, remove orphanOrder from orphanPageOrder
orphanPageOrder.splice(idx, 1);
}
});
});

count += 1;
}

/**
* It converts grouped pages(object) to array
*/
Object.values(orderGroupedByParent).flatMap(arr => [ ...arr ])
.forEach(arr => {
result.push(pages.get(arr));
});

/**
* If the pageId passed, it excludes itself from result pages
* Otherwise just returns result itself
*/
if (pageId) {
return this.removeChildren(result, pageId).reduce((prev, curr) => {
if (curr instanceof Page) {
prev.push(curr);
}

return prev;
}, Array<Page>());
} else {
return result;
}
}

/**
* Set all children elements to null
*
* @param {Array<Page|null>} [pagesAvailable] - Array of all pages
* @param {string} parent - id of parent page
* @returns {Array<?Page>}
*/
public static removeChildren(pagesAvailable: Array<Page|null>, parent: string | undefined): Array<Page | null> {
public static removeChildren(pagesAvailable: Array<Page | null>, parent: string | undefined): Array<Page | null> {
pagesAvailable.forEach(async (item, index) => {
if (item === null || item._parent !== parent) {
return;
Expand Down
18 changes: 18 additions & 0 deletions src/backend/controllers/pagesOrder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@ class PagesOrder {
return PageOrder.getAll();
}

/**
* Returns only root page's order
*
* @returns {Promise<PageOrder[]>}
*/
public static async getRootPageOrder(): Promise<PageOrder> {
return PageOrder.getRootPageOrder();
}

/**
* Returns only child page's order
*
* @returns {Promise<PageOrder[]>}
*/
public static async getChildPageOrder(): Promise<PageOrder[]> {
return PageOrder.getChildPageOrder();
}

/**
* Pushes the child page to the parent's order list
*
Expand Down
22 changes: 22 additions & 0 deletions src/backend/models/pageOrder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,28 @@ class PageOrder {
return Promise.all(docs.map(doc => new PageOrder(doc)));
}

/**
* Returns only root page's order
*
* @returns {Promise<PageOrder[]>}
*/
public static async getRootPageOrder(): Promise<PageOrder> {
const docs = await db.findOne({ 'page': '0' });

return new PageOrder(docs);
}

/**
* Returns only child page's order
*
* @returns {Promise<PageOrder[]>}
*/
public static async getChildPageOrder(): Promise<PageOrder[]> {
const docs = await this.getAll({ 'page': { $ne: '0' } });

return Promise.all(docs.map(doc => new PageOrder(doc)));
}

/**
* constructor data setter
*
Expand Down
7 changes: 4 additions & 3 deletions src/backend/routes/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ const router = express.Router();
*/
router.get('/page/new', verifyToken, allowEdit, async (req: Request, res: Response, next: NextFunction) => {
try {
const pagesAvailable = await Pages.getAll();
const pagesAvailableGrouped = await Pages.groupByParent();

res.render('pages/form', {
pagesAvailable,
pagesAvailableGrouped,
page: null,
});
} catch (error) {
Expand All @@ -32,6 +32,7 @@ router.get('/page/edit/:id', verifyToken, allowEdit, async (req: Request, res: R
try {
const page = await Pages.get(pageId);
const pagesAvailable = await Pages.getAllExceptChildren(pageId);
const pagesAvailableGrouped = await Pages.groupByParent(pageId);

if (!page._parent) {
throw new Error('Parent not found');
Expand All @@ -42,7 +43,7 @@ router.get('/page/edit/:id', verifyToken, allowEdit, async (req: Request, res: R
res.render('pages/form', {
page,
parentsChildrenOrdered,
pagesAvailable,
pagesAvailableGrouped,
});
} catch (error) {
res.status(404);
Expand Down
2 changes: 1 addition & 1 deletion src/backend/views/pages/form.twig
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
{% endif %}
<select name="parent">
<option value="0">Root</option>
{% for _page in pagesAvailable %}
{% for _page in pagesAvailableGrouped %}
{% if _page._id != currentPageId %}
<option value="{{ _page._id }}" {{ page is not empty and page._parent == _page._id ? 'selected' : ''}}>
{% if _page._parent != "0" %}
Expand Down
27 changes: 27 additions & 0 deletions src/test/models/pageOrder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,31 @@ describe('PageOrder model', () => {

await pageOrder.destroy();
});

it('Testing get parents and children order methods', async () => {
const parentTestData = {
page: '0',
order: ['1', '2', '3', '4', '5'],
};
const childTestData = {
page: 'child',
order: ['a', 'b', 'c', 'd', 'e'],
};

const parentOrder = new PageOrder(parentTestData);
const childOrder = new PageOrder(childTestData);
const insertedParentOrder = await parentOrder.save();
const insertedChildOrder = await childOrder.save();
const fetchedParentOrder = await PageOrder.getRootPageOrder();
const fetchedChildOrder = await PageOrder.getChildPageOrder();

expect(fetchedParentOrder.page).to.deep.equals(parentTestData.page);
expect(fetchedParentOrder.order).to.deep.equal(parentTestData.order);
expect(fetchedChildOrder).to.be.an('array').that.is.length(1);
expect(fetchedChildOrder[0].page).to.deep.equals(childTestData.page);
expect(fetchedChildOrder[0].order).to.deep.equals(childTestData.order);

await insertedParentOrder.destroy();
await insertedChildOrder.destroy();
});
});