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: ability to create 404 page via Registry UI #289

Merged
merged 3 commits into from
May 5, 2021
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
2 changes: 1 addition & 1 deletion ilc/server/tailor/fetch-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module.exports = (configsInjector, newrelic, registryService) => async (
throw new Error('Can\'t match route base template to config map');
}

const routeName = currRoute.route.replace(/^\/(.+)/, '$1');
const routeName = currRoute.route?.replace(/^\/(.+)/, '$1') || `special:${currRoute.specialRole}`;
if (routeName) {
newrelic.setTransactionName(routeName);
}
Expand Down
4 changes: 2 additions & 2 deletions registry/client/src/JsonField/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {JsonEditor} from "jsoneditor-react";
import ace from "brace";
import 'brace/mode/json';

const style = {height: '400px'};
const style = {height: '300px'};

export default ({
label,
Expand Down Expand Up @@ -60,7 +60,7 @@ export default ({

<JsonEditor
ref={setJsonEditorRef}
style={style}
htmlElementProps={{ style }}
mode="code"
value={jsonVal}
ace={ace}
Expand Down
20 changes: 16 additions & 4 deletions registry/client/src/appRoutes/Edit.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React, { useState, useEffect } from 'react';
import { useLocation } from 'react-router';
import queryString from 'query-string';
import {
Create,
Edit,
Expand Down Expand Up @@ -39,6 +41,10 @@ const allowedAppKinds = [
{ id: 'regular', name: 'Regular' },
];

const allowedSpecialRoles = [
{ id: '404', name: '404' },
];

const InputForm = ({mode = 'edit', ...props}) => {
const [isMultiDomain, setIsMultiDomain] = useState(false);

Expand All @@ -53,20 +59,26 @@ const InputForm = ({mode = 'edit', ...props}) => {
});
}, []);

const location = useLocation();
const isCreateSpecial = !!queryString.parse(location.search).special;
const isUpdateSpecial = !!props.record.specialRole;
const isSpecial = isCreateSpecial || isUpdateSpecial;

return (
<TabbedForm {...props}>
<FormTab label="General">
{mode === 'edit'
? <TextField source="id" />
: null}

<FormDataConsumer>
{({ formData, ...rest }) => !formData.specialRole && [
{isSpecial
? <SelectInput resettable source="specialRole" label="Special role" validate={[required()]} choices={allowedSpecialRoles} />
: [
<NumberInput source="orderPos" validate={[required()]} />,
<TextInput source="route" fullWidth validate={[required()]} />,
<BooleanInput source="next" />
<BooleanInput source="next" defaultValue={false} />
]}
</FormDataConsumer>

<ReferenceInput reference="template"
source="templateName"
label="Template name">
Expand Down
42 changes: 36 additions & 6 deletions registry/client/src/appRoutes/List.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ import {
BooleanField,
Filter,
BooleanInput,
ReferenceInput,
SelectInput,
ReferenceField,
Link,
TopToolbar,
Button,
sanitizeListRestProps,
} from 'react-admin'; // eslint-disable-line import/no-unresolved
import IconAdd from '@material-ui/icons/Add';


import dataProvider from '../dataProvider';

Expand Down Expand Up @@ -49,29 +54,29 @@ const ListActionsToolbar = ({ children, ...props }) => {
);
};

const ListFilter = (props) => {
const ListFilter = ({ routerDomain, ...props }) => {
const classes = useStyles();

return (
<Filter {...props} className={classes.filters}>
<BooleanInput label="Show special" source="showSpecial" alwaysOn className={classes.filtersSpecial} />
{
props.routerDomain.length
routerDomain.length
? <SelectInput
alwaysOn
source="domainId"
label="Domain"
optionText="domainName"
resettable
choices={props.routerDomain}
choices={routerDomain}
/>
: null
}
</Filter>
);
};

const ListGrid = (props) => {
const ListGrid = ({ routerDomain, ...props }) => {
return (
<Datagrid {...props} rowClick="edit" optimized>
{!props.filterValues.showSpecial ? <TextField source="id" sortable={false} /> : null }
Expand All @@ -83,7 +88,7 @@ const ListGrid = (props) => {
<TextField source="name" />
</ReferenceField>
{
props.routerDomain.length
routerDomain.length
? <ReferenceField label="Domain Name" source="domainId" reference="router_domains" emptyText="-" sortable={false}>
<TextField source="domainName" />
</ReferenceField>
Expand All @@ -97,6 +102,30 @@ const ListGrid = (props) => {
);
};

const ListActions = ({
className,
resource,
filters,
displayedFilters,
filterValues,
showFilter,
...rest
}) => (
<TopToolbar className={className} {...sanitizeListRestProps(rest)}>
{filters && cloneElement(filters, {
resource,
showFilter,
displayedFilters,
filterValues,
context: 'button',
})}
<Link to={`/route/create${filterValues.showSpecial ? '?special=1' : ''}`}>
<Button label={`create ${filterValues.showSpecial ? 'special route' : ''}`}>
<IconAdd />
</Button>
</Link>
</TopToolbar>
);

const PostList = props => {
const isSmall = useMediaQuery(theme => theme.breakpoints.down('sm'));
Expand Down Expand Up @@ -124,6 +153,7 @@ const PostList = props => {
<List
{...props}
bulkActionButtons={<ListBulkActions />}
actions={<ListActions />}
exporter={false}
filters={<ListFilter routerDomain={routerDomain} />}
perPage={25}
Expand Down
2 changes: 2 additions & 0 deletions registry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"knex": "knex",
"migrate": "knex --knexfile config/knexfile.ts migrate:latest",
"migrate:make": "knex --knexfile config/knexfile.ts migrate:make",
"migrate:rollback": "knex --knexfile config/knexfile.ts migrate:rollback",
"seed": "knex --knexfile config/knexfile.ts seed:run",
"seed:make": "knex --knexfile config/knexfile.ts seed:make -x ts",
"test": "cross-env NODE_ENV=test mocha --exclude=client/node_modules/**",
Expand Down Expand Up @@ -74,6 +75,7 @@
"passport": "^0.4.1",
"passport-http-bearer": "^1.0.1",
"passport-local": "^1.0.0",
"query-string": "^7.0.0",
"serve-static": "^1.14.1",
"source-map-support": "^0.5.19",
"sqlite3": "^5.0.0",
Expand Down
9 changes: 6 additions & 3 deletions registry/server/appRoutes/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,12 @@ export const partialAppRouteSchema = Joi.object({
...commonAppRoute,
});

const conditionSpecialRole = { is: Joi.exist(), then: Joi.forbidden(), otherwise: Joi.required() };

export const appRouteSchema = Joi.object({
...commonAppRoute,
orderPos: commonAppRoute.orderPos.required(),
route: commonAppRoute.route.required(),
slots: commonAppRoute.slots.required(),
});
orderPos: commonAppRoute.orderPos.when('specialRole', conditionSpecialRole),
route: commonAppRoute.route.when('specialRole', conditionSpecialRole),
next: commonAppRoute.next.when('specialRole', conditionSpecialRole),
});
76 changes: 39 additions & 37 deletions registry/server/appRoutes/routes/createAppRoute.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import {
Request,
Response,
} from 'express';
import { Request, Response } from 'express';
import _ from 'lodash/fp';

import db from '../../db';
import validateRequestFactory from '../../common/services/validateRequest';
import {
stringifyJSON,
} from '../../common/services/json';
import {
prepareAppRouteToRespond,
prepareAppRouteToSave,
} from '../services/prepareAppRoute';
import {
appRouteSchema,
} from '../interfaces';
import { stringifyJSON } from '../../common/services/json';
import { prepareAppRouteToSave } from '../services/prepareAppRoute';
import { appRouteSchema } from '../interfaces';
import * as httpErrors from '../../errorHandler/httpErrors';
import { retrieveAppRouteFromDB } from './getAppRoute';
import { transformSpecialRoutesForDB } from '../services/transformSpecialRoutes';

const validateRequestBeforeCreateAppRoute = validateRequestFactory([{
schema: appRouteSchema,
Expand All @@ -25,35 +18,44 @@ const validateRequestBeforeCreateAppRoute = validateRequestFactory([{
const createAppRoute = async (req: Request, res: Response) => {
const {
slots: appRouteSlots,
...appRoute
...appRouteData
} = req.body;

const appRoute = transformSpecialRoutesForDB(appRouteData);

let savedAppRouteId: number;

await db.versioning(req.user, { type: 'routes' }, async (transaction) => {
[savedAppRouteId] = await db('routes').insert(prepareAppRouteToSave(appRoute)).transacting(transaction);

await db.batchInsert('route_slots', _.compose(
_.map((appRouteSlotName) => _.compose(
stringifyJSON(['props']),
_.assign({ name: appRouteSlotName, routeId: savedAppRouteId }),
_.get(appRouteSlotName)
)(appRouteSlots)),
_.keys,
)(appRouteSlots)).transacting(transaction);

return savedAppRouteId;
});

const savedAppRoute = await db
.select('routes.id as routeId', 'route_slots.id as routeSlotId', 'routes.*', 'route_slots.*')
.from('routes')
.where('routeId', savedAppRouteId!)
.join('route_slots', {
'route_slots.routeId': 'routes.id'
try {
await db.versioning(req.user, { type: 'routes' }, async (transaction) => {
[savedAppRouteId] = await db('routes').insert(prepareAppRouteToSave(appRoute)).transacting(transaction);

await db.batchInsert('route_slots', _.compose(
_.map((appRouteSlotName) => _.compose(
stringifyJSON(['props']),
_.assign({ name: appRouteSlotName, routeId: savedAppRouteId }),
_.get(appRouteSlotName)
)(appRouteSlots)),
_.keys,
)(appRouteSlots)).transacting(transaction);

return savedAppRouteId;
});
} catch (e) {
let { message } = e;

// error messages for uniq constraint "route" and "domainId"
const sqliteError = 'UNIQUE constraint failed: routes.route, routes.domainId';
const mysqlError = 'routes_route_and_domainId_unique';

if (message.includes(sqliteError) || message.includes(mysqlError)) {
message = `SpecialRole "${req.body.specialRole}" for provided DomainId "${req.body.domainId}" already exists`;
}
throw new httpErrors.DBError({ message })
}

const savedAppRoute = await retrieveAppRouteFromDB(savedAppRouteId!);

res.status(200).send(prepareAppRouteToRespond(savedAppRoute));
res.status(200).send(savedAppRoute);
};

export default [validateRequestBeforeCreateAppRoute, createAppRoute];
22 changes: 15 additions & 7 deletions registry/server/appRoutes/routes/deleteAppRoute.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import {
Request,
Response,
} from 'express';
import { Request, Response } from 'express';
import Joi from 'joi';
import * as httpErrors from '../../errorHandler/httpErrors';

import db from '../../db';
import validateRequestFactory from '../../common/services/validateRequest';
import {
appRouteIdSchema,
} from '../interfaces';
import { appRouteIdSchema } from '../interfaces';
import { makeSpecialRoute } from '../services/transformSpecialRoutes';

type DeleteAppRouteRequestParams = {
id: string
Expand All @@ -22,9 +18,21 @@ const validateRequestBeforeDeleteAppRoute = validateRequestFactory([{
selector: 'params',
}]);

let idDefault404: number | undefined;

const deleteAppRoute = async (req: Request<DeleteAppRouteRequestParams>, res: Response) => {
if (!idDefault404) {
const [default404] = await db.select().from('routes').where({ route: makeSpecialRoute('404'), domainId: null});

idDefault404 = default404?.id;
}

const appRouteId = req.params.id;

if (idDefault404 === +appRouteId) {
throw new httpErrors.CustomError({ message: 'Default 404 error can\'t be deleted' });
}

await db.versioning(req.user, {type: 'routes', id: appRouteId}, async (transaction) => {
await db('route_slots').where('routeId', appRouteId).delete().transacting(transaction);
const count = await db('routes').where('id', appRouteId).delete().transacting(transaction);
Expand Down
Loading