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

feat: support a few domains (500) #283

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions e2e/codecept.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ exports.config = {
newsPage: path.join(__dirname, 'spec', 'pages', 'news.ts'),
planetsPage: path.join(__dirname, 'spec', 'pages', 'planets.ts'),
hooksPage: path.join(__dirname, 'spec', 'pages', 'hooks.ts'),
common: path.join(__dirname, 'spec', 'pages', 'common.ts'),
},
tests: './spec/**/*.spec.e2e.ts',
name: 'ilc',
Expand Down
39 changes: 39 additions & 0 deletions e2e/spec/500.spec.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Feature('500 error handling');

//region 500 page
Scenario('Renders 500 page for domain "localhost:8233" (default)', (I, common: common) => {
I.amOnPage(common.url.urlInternalServerError);
I.seeInSource(common.textError500);
I.seeInSource(common.textErrorId);
I.dontSeeInSource(common.textError500ForLocalhostAsIPv4);
I.seeInCurrentUrl(common.url.urlInternalServerError);
});
Scenario('should open 500 error page when an error happens for domain "localhost:8233" (default)', async (I, newsPage: newsPage, common: common) => {
I.amOnPage(newsPage.url.main);
I.waitInUrl(newsPage.url.main, 10);
I.waitForElement(newsPage.generateError, 10);
I.click(newsPage.generateError);
I.seeInSource(common.textError500);
I.seeInSource(common.textErrorId);
I.dontSeeInSource(common.textError500ForLocalhostAsIPv4);
I.seeInCurrentUrl(newsPage.url.main);
});

Scenario('Renders 500 page for domain "127.0.0.1:8233"', (I, common: common) => {
I.amOnPage(common.url.localhostAsIPv4 + common.url.urlInternalServerError);
I.seeInSource(common.textError500ForLocalhostAsIPv4);
I.seeInSource(common.textErrorId);
I.dontSeeInSource(common.textError500);
I.seeInCurrentUrl(common.url.localhostAsIPv4 + common.url.urlInternalServerError);
});
Scenario('should open 500 error page when an error happens for domain "127.0.0.1:8233"', async (I, newsPage: newsPage, common: common) => {
I.amOnPage(common.url.localhostAsIPv4 + newsPage.url.main);
I.waitInUrl(newsPage.url.main, 10);
I.waitForElement(newsPage.generateError, 10);
I.click(newsPage.generateError);
I.seeInSource(common.textError500ForLocalhostAsIPv4);
I.seeInSource(common.textErrorId);
I.dontSeeInSource(common.textError500);
I.seeInCurrentUrl(common.url.localhostAsIPv4 + newsPage.url.main);
});
//endregion 500 page
9 changes: 0 additions & 9 deletions e2e/spec/news.spec.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,3 @@ Scenario('should open an article page from a direct link', async (I, newsPage: n
I.seeInCurrentUrl(lastNewsSourceLinkHref);
I.closeOtherTabs();
});

Scenario('should open 500 error page when an error happens', async (I, newsPage: newsPage) => {
I.amOnPage(newsPage.url.main);
I.waitInUrl(newsPage.url.main, 10);
I.waitForElement(newsPage.generateError, 10);
I.click(newsPage.generateError);
I.waitForElement(newsPage.errorId);
I.seeInCurrentUrl(newsPage.url.main);
});
8 changes: 8 additions & 0 deletions e2e/spec/pages/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const url = {
urlInternalServerError: '/_ilc/500',
localhostAsIPv4: 'http://127.0.0.1:8233',
};

export const textError500 = '<h3>ERROR 500</h3>';
export const textError500ForLocalhostAsIPv4 = '<h3>500 Internal Server Error on 127.0.0.1</h3>';
export const textErrorId = 'Error ID:';
1 change: 0 additions & 1 deletion e2e/spec/pages/news.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export const newsView = 'body > div#body > div.single-spa-container.news-app > d
export const newsSources = `${newsView} > div.sources > div.container > ol > li.source`;
export const bannerHeadline = `${newsView} > div.banner > h1`;
export const generateError = `${newsView} > div.banner > a`;
export const errorId = `body > div#error > p:nth-child(3)`;
export const lastNewsSource = `${newsSources}:last-child`;
export const lastNewsSourceLink = `${lastNewsSource} > p.action > a`;
export const newsSourceArticles = `${newsView} > div.container > div.articles > ol > li.article`;
Expand Down
5 changes: 3 additions & 2 deletions e2e/typings/steps.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ type peoplePage = typeof import('../spec/pages/people');
type newsPage = typeof import('../spec/pages/news');
type planetsPage = typeof import('../spec/pages/planets');
type hooksPage = typeof import('../spec/pages/hooks');
type common = typeof import('../spec/pages/common');
type MockRequestHelper = import('@codeceptjs/mock-request');

declare namespace CodeceptJS {
interface SupportObject { I: CodeceptJS.I, peoplePage: peoplePage, newsPage: newsPage, planetsPage: planetsPage, hooksPage: hooksPage }
interface CallbackOrder { [0]: CodeceptJS.I; [1]: peoplePage; [2]: newsPage; [3]: planetsPage; [4]: hooksPage }
interface SupportObject { I: CodeceptJS.I, peoplePage: peoplePage, newsPage: newsPage, planetsPage: planetsPage, hooksPage: hooksPage, common: common }
interface CallbackOrder { [0]: CodeceptJS.I; [1]: peoplePage; [2]: newsPage; [3]: planetsPage; [4]: hooksPage; [5]: common }
interface Methods extends CodeceptJS.Puppeteer, MockRequestHelper {}
interface I extends WithTranslation<Methods> {}
namespace Translation {
Expand Down
3 changes: 2 additions & 1 deletion ilc/common/wrapWithCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ errors.WrapWithCacheError = extendError('WrapWithCacheError');
const wrapWithCache = (localStorage, logger, createHash = hashFn) => (fn, cacheParams = {}) => {
const {
cacheForSeconds = 60,
name = '', // "hash" of returned value is based only on arguments, so with the help "name" we can add prefix to hash
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a required parameter.

} = cacheParams;

const cacheResolutionPromise = {};

return (...args) => {
const now = Math.floor(Date.now() / 1000);
const hash = args.length > 0 ? createHash(JSON.stringify(args)) : '__null__';
const hash = `${name ? name + '__' : ''}${args.length > 0 ? createHash(JSON.stringify(args)) : '__null__'}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass name to hash function. Don't do a concatenation after

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So now we would use hash fn even if we don't have args


if (localStorage.getItem(hash) === null || JSON.parse(localStorage.getItem(hash)).cachedAt < now - cacheForSeconds) {
if (cacheResolutionPromise[hash] !== undefined) {
Expand Down
3 changes: 2 additions & 1 deletion ilc/server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ module.exports = (registryService, pluginManager) => {
app.register(require('./ping'));

app.get('/_ilc/api/v1/registry/template/:templateName', async (req, res) => {
const data = await registryService.getTemplate(req.params.templateName);
const currentDomain = req.hostname;
const data = await registryService.getTemplate(req.params.templateName, currentDomain);
res.status(200).send(data.data.content);
});

Expand Down
3 changes: 2 additions & 1 deletion ilc/server/errorHandler/ErrorHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ module.exports = class ErrorHandler {
errorId
});

let data = await this.#registryService.getTemplate('500');
const currentDomain = req.hostname;
let data = await this.#registryService.getTemplate('500', currentDomain);
data = data.data.content.replace('%ERRORID%', `Error ID: ${errorId}`);

nres.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
Expand Down
2 changes: 2 additions & 0 deletions ilc/server/errorHandler/ErrorHandler.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe('ErrorHandler', () => {
});

it('should show 500 error page with an error id', async () => {
nock(config.get('registry').address).get('/api/v1/router_domains').reply(200, []);
nock(config.get('registry').address).get(`/api/v1/template/500/rendered`).reply(200, {
content:
'<html>' +
Expand Down Expand Up @@ -59,6 +60,7 @@ describe('ErrorHandler', () => {
});

it('should send an error message when showing 500 error page throws an error', async () => {
nock(config.get('registry').address).get('/api/v1/router_domains').reply(200, []);
const replyingError = new Error('Something awful happened.');

nock(config.get('registry').address).get(`/api/v1/template/500/rendered`).replyWithError(replyingError.message);
Expand Down
43 changes: 41 additions & 2 deletions ilc/server/registry/Registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = class Registry {
#cacheHeated = {
config: false,
template: false,
routerDomains: false,
};

/**
Expand All @@ -30,6 +31,7 @@ module.exports = class Registry {

const getConfigMemo = wrapFetchWithCache(this.#getConfig, {
cacheForSeconds: 5,
name: 'registry_getConfig',
});

this.getConfig = async (options) => {
Expand All @@ -38,13 +40,28 @@ module.exports = class Registry {
return res;
};

this.getTemplate = wrapFetchWithCache(this.#getTemplate, {
this.getRouterDomains = wrapFetchWithCache(this.#getRouterDomains, {
cacheForSeconds: 30,
name: 'registry_routerDomains',
});

const getTemplateMemo = wrapFetchWithCache(this.#getTemplate, {
cacheForSeconds: 30,
});

this.getTemplate = async (templateName, forDomain) => {
if (templateName === '500' && forDomain) {
const routerDomains = await this.getRouterDomains();
const redefined500 = routerDomains.data.find(item => item.domainName === forDomain)?.template500;
templateName = redefined500 || templateName;
}

return await getTemplateMemo(templateName);
};
}

async preheat() {
if (this.#cacheHeated.template && this.#cacheHeated.config) {
if (this.#cacheHeated.template && this.#cacheHeated.config && this.#cacheHeated.routerDomains) {
return;
}

Expand All @@ -53,6 +70,7 @@ module.exports = class Registry {
await Promise.all([
this.getConfig(),
this.getTemplate('500'),
this.getRouterDomains(),
]);

this.#logger.info('Registry preheated successfully!');
Expand Down Expand Up @@ -102,6 +120,27 @@ module.exports = class Registry {
return res.data;
};

#getRouterDomains = async () => {
this.#logger.debug('Calling get routerDomains registry endpoint...');

const url = urljoin(this.#address, 'api/v1/router_domains');
let res;
try {
res = await axios.get(url, { responseType: 'json' });
} catch (e) {
throw new errors.RegistryError({
message: `Error while requesting routerDomains from registry`,
cause: e,
data: {
requestedUrl: url
}
});
}

this.#cacheHeated.routerDomains = true;
return res.data;
};

#filterConfig = (config, filter) => {
if (!filter || !Object.keys(filter).length) {
return config;
Expand Down
9 changes: 8 additions & 1 deletion registry/client/src/routerDomains/Edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
TextInput,
required,
TextField,
ReferenceInput,
SelectInput,
} from 'react-admin'; // eslint-disable-line import/no-unresolved

const Title = ({ record }) => {
Expand All @@ -19,7 +21,12 @@ const InputForm = ({ mode = 'edit', ...props }) => {
? <TextField source="id" />
: null}

<TextInput source="domainName" fullWidth validate={required()} />
<TextInput source="domainName" fullWidth validate={required()} />
<ReferenceInput reference="template"
source="template500"
label="Template of 500 error">
<SelectInput resettable optionText="name" helperText="Default template name is '500'" />
</ReferenceInput>
</SimpleForm>
);
};
Expand Down
1 change: 1 addition & 0 deletions registry/client/src/routerDomains/List.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const PostList = props => {
<Datagrid rowClick="edit" optimized>
<TextField source="id" sortable={false} />
<TextField source="domainName" sortable={false} />
<TextField source="template500" sortable={false} emptyText="-" />
<ListActionsToolbar>
<EditButton />
</ListActionsToolbar>
Expand Down
2 changes: 1 addition & 1 deletion registry/server/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default (withAuth: boolean = true) => {
app.use('/api/v1/auth_entities', authMw, routes.authEntities);
app.use('/api/v1/versioning', authMw, routes.versioning);
app.use('/api/v1/settings', routes.settings(authMw));
app.use('/api/v1/router_domains', authMw, routes.routerDomains);
app.use('/api/v1/router_domains', routes.routerDomains(authMw));

app.use(errorHandler);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export async function up(knex: Knex): Promise<any> {
return knex.schema.createTable('router_domains', table => {
table.increments('id');
table.string('domainName', 255).notNullable();
table.string('template500', 50).nullable().references('templates.name')
});
}

Expand Down
12 changes: 9 additions & 3 deletions registry/server/routerDomains/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import Joi from 'joi';
import { templateNameSchema } from '../../templates/interfaces';

export default interface RouterDomains {
id: number,
domainName: string,
template500?: string,
};

export const routerDomainIdSchema = Joi.string().trim().required();

const routerDomainNameSchema = Joi.string().trim().min(1);
const commonRouterDomainsSchema = {
domainName: Joi.string().trim().min(1),
template500: templateNameSchema.allow(null),
};

export const partialRouterDomainsSchema = Joi.object({
domainName: routerDomainNameSchema,
...commonRouterDomainsSchema,
});

export const routerDomainsSchema = Joi.object({
domainName: routerDomainNameSchema.required(),
...commonRouterDomainsSchema,
domainName: commonRouterDomainsSchema.domainName.required(),
});
18 changes: 10 additions & 8 deletions registry/server/routerDomains/routes/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import express from 'express';
import express, { RequestHandler } from 'express';

import getRouterDomains from './getRouterDomains';
import getAllRouterDomains from './getAllRouterDomains';
import updateRouterDomains from './updateRouterDomains';
import createRouterDomains from './createRouterDomains';
import deleteRouterDomains from './deleteRouterDomains';

const routerDomainsRouter = express.Router();
export default (authMw: RequestHandler) => {
const routerDomainsRouter = express.Router();

routerDomainsRouter.get('/', ...getAllRouterDomains);
routerDomainsRouter.post('/', ...createRouterDomains);
routerDomainsRouter.get('/:id', ...getRouterDomains);
routerDomainsRouter.put('/:id', ...updateRouterDomains);
routerDomainsRouter.delete('/:id', ...deleteRouterDomains);
routerDomainsRouter.get('/', ...getAllRouterDomains);
routerDomainsRouter.post('/', authMw, ...createRouterDomains);
routerDomainsRouter.get('/:id', authMw, ...getRouterDomains);
routerDomainsRouter.put('/:id', authMw, ...updateRouterDomains);
routerDomainsRouter.delete('/:id', authMw, ...deleteRouterDomains);

export default routerDomainsRouter;
return routerDomainsRouter;
};
3 changes: 2 additions & 1 deletion registry/server/seeds/00_cleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ export async function seed(knex: Knex): Promise<any> {
await knex('route_slots').transacting(trx).truncate();
await knex('routes').transacting(trx).truncate();
await knex('apps').transacting(trx).truncate();
await knex('templates').transacting(trx).truncate();
await knex('shared_props').transacting(trx).truncate();
await knex('router_domains').transacting(trx).truncate();
await knex('templates').transacting(trx).truncate();
} finally {
isMySQL(knex) && await knex.schema.raw('SET FOREIGN_KEY_CHECKS = 1;').transacting(trx);
}
Expand Down
6 changes: 6 additions & 0 deletions registry/server/seeds/02_templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,11 @@ export async function seed(knex: Knex): Promise<any> {
encoding: 'utf8',
})
},
{
name: '500ForLocalhostAsIPv4',
content: fs.readFileSync(path.join(__dirname, './data/templates/500ForLocalhostAsIPv4.html'), {
encoding: 'utf8',
})
},
]);
}
11 changes: 11 additions & 0 deletions registry/server/seeds/06_routerDomains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as Knex from 'knex';

export async function seed(knex: Knex): Promise<any> {
return knex("router_domains").insert([
{
id: 1,
domainName: '127.0.0.1:8233',
template500: '500ForLocalhostAsIPv4',
},
]);
}
Loading