Skip to content

Commit

Permalink
Forbid spa bundle
Browse files Browse the repository at this point in the history
  • Loading branch information
Oleh Momot committed Oct 6, 2020
1 parent 53d3af6 commit 9b6d57e
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 135 deletions.
136 changes: 38 additions & 98 deletions registry/client/src/apps/Edit.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import React, {
useState,
} from 'react';

import {
useForm,
} from 'react-final-form';
import React, {Fragment} from 'react';

import {
Create,
Expand All @@ -13,128 +7,69 @@ import {
SelectInput,
TabbedForm,
TextInput,
NumberInput,
required,
ArrayInput,
SimpleFormIterator,
FormDataConsumer,
NumberInput,
TextField,
ReferenceArrayInput,
AutocompleteArrayInput,
FormDataConsumer,
useDataProvider,
} from 'react-admin'; // eslint-disable-line import/no-unresolved

import JsonField from '../JsonField/index';
import * as validators from '../validators';

const Title = ({record}) => {
return (<span>{record ? `App "${record.name}"` : ''}</span>);
};

const selectFetchingManifestStatusText = (isFetchingManifest) => {
if (!isFetchingManifest) {
const selectHasAssetsDiscoveryUrl = (formData) => formData.assetsDiscoveryUrl && formData.assetsDiscoveryUrl.length !== 0;
const selectWarnMessageDueToAssetsDiscoveryUrl = (formData) => {
if (!selectHasAssetsDiscoveryUrl(formData)) {
return '';
}

return 'Trying to fetch the manifest file...';
return `Do not need to specify SPA bundle, CSS bundle, dependencies because they would be fetched and set from assets discovery URL if they exist there`;
};

const InputForm = ({mode = 'edit', ...props}) => {
const [assetsDiscoveryError, setAssetsDiscoveryError] = useState(null);
const [isFetchingManifest, setFetchingManifest] = useState(false);
const hasError = !!assetsDiscoveryError;
const fetchingManifestHelperText = selectFetchingManifestStatusText(isFetchingManifest);

return (
<TabbedForm initialValues={{dependencies: []}} {...props}>
<FormTab label="Summary">
{mode === 'edit'
? <TextField source="name" />
: <TextInput source="name" fullWidth validate={required()} />}
: <TextInput source="name" fullWidth validate={validators.required} />}
<SelectInput source="kind" choices={[
{id: 'primary', name: 'Primary'},
{id: 'essential', name: 'Essential'},
{id: 'regular', name: 'Regular'},
]} />
<ReferenceArrayInput reference="shared_props" source="configSelector" label="Shared props selector">
<AutocompleteArrayInput />
</ReferenceArrayInput>
</FormTab>
<FormTab label="Assets">
<FormDataConsumer>
{() => {
const form = useForm();
const dataProvider = useDataProvider();
const error = isFetchingManifest ? false : hasError;
const errorMessage = hasError && assetsDiscoveryError.message;
const helperText = fetchingManifestHelperText || errorMessage;
{({formData, ...rest}) => {
const hasAssetsDiscoveryUrl = selectHasAssetsDiscoveryUrl(formData);
const assetsDiscoveryUrlWarningText = selectWarnMessageDueToAssetsDiscoveryUrl(formData);
const spaBundleValidators = [];

return (
<TextInput
fullWidth
resettable
type="url"
source="assetsDiscoveryUrl"
disabled={isFetchingManifest}
error={error}
helperText={helperText}
onBlur={(event) => {
const assetsDiscoveryUrl = event.target.value;

if (!assetsDiscoveryUrl) {
setAssetsDiscoveryError(null);
return;
}

setFetchingManifest(true);

return dataProvider.getOneAssetsDiscoveryManifest(assetsDiscoveryUrl).then(({data: manifest}) => {
if (!manifest.spaBundle) {
return Promise.reject(new Error('"spaBundle" must be specified in the manifest file from provided "assetsDiscoveryUrl"'));
}
if (!hasAssetsDiscoveryUrl) {
spaBundleValidators.push(validators.url, validators.required);
}

if (manifest.cssBundle) {
form.change('cssBundle', manifest.cssBundle);
}

form.change('spaBundle', manifest.spaBundle);
setAssetsDiscoveryError(null);
}).catch((error) => {
setAssetsDiscoveryError(error);
}).then(() => {
setFetchingManifest(false);
});
}}
/>
return (
<Fragment>
<TextInput fullWidth resettable type="url" source="assetsDiscoveryUrl" helperText={assetsDiscoveryUrlWarningText} validate={validators.url} />
<TextInput fullWidth resettable type="url" source="spaBundle" disabled={hasAssetsDiscoveryUrl} validate={spaBundleValidators} />
<TextInput fullWidth resettable type="url" source="cssBundle" validate={validators.url} />
<ArrayInput source="dependencies">
<SimpleFormIterator>
<TextInput fullWidth label="Name" source="key" />
<TextInput fullWidth label="URL" source="value" />
</SimpleFormIterator>
</ArrayInput>
</Fragment>
);
}}
</FormDataConsumer>
<ReferenceArrayInput
reference="shared_props"
source="configSelector"
label="Shared props selector"
>
<AutocompleteArrayInput />
</ReferenceArrayInput>
</FormTab>
<FormTab label="Assets">
<TextInput
fullWidth
resettable
type="url"
source="spaBundle"
validate={required()}
disabled={isFetchingManifest}
helperText={fetchingManifestHelperText}
/>
<TextInput
fullWidth
resettable
type="url"
source="cssBundle"
disabled={isFetchingManifest}
helperText={fetchingManifestHelperText}
/>
<ArrayInput source="dependencies">
<SimpleFormIterator>
<TextInput source="key" label="Name" fullWidth />
<TextInput source="value" label="URL" fullWidth />
</SimpleFormIterator>
</ArrayInput>
</FormTab>
<FormTab label="SSR">
<TextInput source="ssr.src" label="URL" fullWidth />
Expand All @@ -147,11 +82,16 @@ const InputForm = ({mode = 'edit', ...props}) => {
);
};

const Title = ({record}) => {
return (<span>{record ? `App "${record.name}"` : ''}</span>);
};

export const MyEdit = ({permissions, ...props}) => (
<Edit title={<Title />} undoable={false} {...props}>
<InputForm mode="edit" />
</Edit>
);

export const MyCreate = ({permissions, ...props}) => {
return (
<Create {...props}>
Expand Down
3 changes: 3 additions & 0 deletions registry/client/src/apps/dataTransform.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export function transformSet(app) {
return acc;
}, {})
}
if (app.assetsDiscoveryUrl) {
delete app.spaBundle;
}
delete app.id;
delete app.assetsDiscoveryUpdatedAt;
}
15 changes: 0 additions & 15 deletions registry/client/src/dataProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,6 @@ const myDataProvider = {
)
).then(() => ({data: params.ids}));
},
getOneAssetsDiscoveryManifest: (resource, params) => {
return fetch(resource).then(response => {
if (response.status < 200 || response.status >= 300) {
return Promise.reject(new Error(response.statusText));
}

return response.text();
}).then(data => {
try {
return JSON.parse(data);
} catch (error) {
return Promise.reject(new Error(`Can not parse received assets data: ${data}!`));
}
}).then((data) => ({data}));
},
};

function transformGetter(resource, data) {
Expand Down
12 changes: 12 additions & 0 deletions registry/client/src/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,15 @@ import {

export const required = createRequiredValidator();
export const number = createNumberValidator();
export const url = (value) => {
if (value === undefined || value.length === 0) {
return;
}

try {
new URL(value);
return;
} catch (error) {
return 'Should be a valid URL';
}
}
4 changes: 2 additions & 2 deletions registry/server/apps/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export const appSchema = Joi.object({
...commonApp,
name: appNameSchema.required(),
spaBundle: Joi.when('assetsDiscoveryUrl', {
is: commonApp.assetsDiscoveryUrl.required(),
then: commonApp.spaBundle.optional(),
is: commonApp.assetsDiscoveryUrl.exist(),
then: commonApp.spaBundle.forbidden(),
otherwise: commonApp.spaBundle.required(),
}),
});
40 changes: 28 additions & 12 deletions registry/server/apps/routes/createApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
} from 'express';
import _ from 'lodash/fp';
import axios from 'axios';
import processManifest from '../../common/services/assetsManifestProcessor';

import processManifest from '../../common/services/assetsManifestProcessor';
import db from '../../db';
import validateRequestFactory from '../../common/services/validateRequest';
import preProcessResponse from '../../common/services/preProcessResponse';
Expand All @@ -21,7 +21,11 @@ const validateRequestBeforeCreateApp = validateRequestFactory([{
selector: _.get('body'),
}]);

const createApp = async (req: Request, res: Response): Promise<void> => {
export const preProcessAppRequest = async (
req: Request,
res: Response,
next: any,
) => {
const app = req.body;

if (!app.spaBundle && app.assetsDiscoveryUrl) {
Expand All @@ -30,32 +34,44 @@ const createApp = async (req: Request, res: Response): Promise<void> => {
try {
response = await axios.get(app.assetsDiscoveryUrl, {responseType: 'json'});
} catch (error) {
console.log('Caught an error while trying to fetch a manifest file:');
console.log(error);

console.error(`Caught an error while trying to fetch a manifest file from '${app.assetsDiscoveryUrl}':`, error);
res.status(422).send(`"spaBundle" can not be taken from a manifest file by provided "assetsDiscoveryUrl"`);
return;
}

const processedManifest = processManifest(app.assetsDiscoveryUrl, response.data);
const {
spaBundle,
cssBundle,
dependencies,
} = processManifest(app.assetsDiscoveryUrl, response.data);

if (!processedManifest.spaBundle) {
res.status(422).send(`"spaBundle" must be specified in the manifest file from provided "assetsDiscoveryUrl" if it was not specified manually`);
if (spaBundle) {
app.spaBundle = spaBundle;
} else {
res.status(422).send('"spaBundle" must be specified in the manifest file from provided "assetsDiscoveryUrl" if it was not specified manually');
return;
}

if (!app.cssBundle && processedManifest.cssBundle) {
app.cssBundle = processedManifest.cssBundle;
if (cssBundle) {
app.cssBundle = cssBundle;
}

app.spaBundle = processedManifest.spaBundle;
if (dependencies) {
app.dependencies = dependencies;
}
}

next();
};

const createApp = async (req: Request, res: Response): Promise<void> => {
const app = req.body;

await db('apps').insert(stringifyJSON(['dependencies', 'props', 'ssr', 'configSelector'], app));

const [savedApp] = await db.select().from<App>('apps').where('name', app.name);

res.status(200).send(preProcessResponse(savedApp));
};

export default [validateRequestBeforeCreateApp, createApp];
export default [validateRequestBeforeCreateApp, preProcessAppRequest, createApp];
5 changes: 4 additions & 1 deletion registry/server/apps/routes/updateApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import App, {
appNameSchema,
partialAppSchema,
} from '../interfaces';
import {
preProcessAppRequest,
} from './createApp';

type UpdateAppRequestParams = {
name: string
Expand Down Expand Up @@ -50,4 +53,4 @@ const updateApp = async (req: Request<UpdateAppRequestParams>, res: Response): P
res.status(200).send(preProcessResponse(updatedApp));
};

export default [validateRequestBeforeUpdateApp, updateApp];
export default [validateRequestBeforeUpdateApp, preProcessAppRequest, updateApp];
Loading

0 comments on commit 9b6d57e

Please sign in to comment.