Skip to content

Commit

Permalink
Merge pull request #367 from codaco/feature/migrate-to-asset-manifest
Browse files Browse the repository at this point in the history
Update to new asset manifest protocol format
  • Loading branch information
wwqrd authored Jan 18, 2019
2 parents 2d03bbc + 7560151 commit e7697b0
Show file tree
Hide file tree
Showing 14 changed files with 242 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ class ContentGrid extends Component {
return (
<Guidance contentId="guidance.editor.content_items">
<div className="stage-editor-section">
<h2>Content Boxes</h2>
<h2>Content Items</h2>
<p>
Use this section to configure up to three content boxes, containing images, video,
Use this section to configure up to three content items, containing images, video,
audio, or text.
</p>
<div className="content-grid">
Expand Down
14 changes: 7 additions & 7 deletions src/components/StageEditor/sections/ContentGrid/Item.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { compose } from 'recompose';
import { get } from 'lodash';
import ItemPreview from './ItemPreview';
import ItemChooser from './ItemChooser';
import ItemEditor from './ItemEditor';
import { sizes } from './sizes';
import withItemMeta from './withItemMeta';
import withFilteredFieldErrors from './withFilteredFieldErrors';

class Item extends Component {
static propTypes = {
Expand Down Expand Up @@ -108,11 +110,9 @@ class Item extends Component {
}
}

const mapStateToProps = (state, { fieldId, form, errors }) => ({
item: form.getValues(state, `${fieldId}`),
error: get(errors, fieldId),
});

export { Item };

export default connect(mapStateToProps)(Item);
export default compose(
withFilteredFieldErrors,
withItemMeta,
)(Item);
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { withPropsOnChange } from 'recompose';
import { get } from 'lodash';

const withFilteredFieldErrors = withPropsOnChange(
['errors', 'fieldId'],
({ errors, fieldId }) => ({
errors: get(errors, fieldId),
}),
);

export default withFilteredFieldErrors;
34 changes: 34 additions & 0 deletions src/components/StageEditor/sections/ContentGrid/withItemMeta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { connect } from 'react-redux';
import { get } from 'lodash';

// TODO: move to selectors
const getAssetManifest = state =>
get(state, 'protocol.present.assetManifest', {});

const mapStateToProps = (state, { fieldId, form }) => {
const field = form.getValues(state, `${fieldId}`);

if (!field) { return {}; }

if (field.type !== 'asset') {
return { item: field };
}

const assetManifest = getAssetManifest(state);
const itemMeta = get(assetManifest, field.content, {});
const item = {
...field,
...itemMeta,
content: itemMeta.source,
};

return {
item,
};
};

const withItemMeta = connect(
mapStateToProps,
);

export default withItemMeta;
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
/* eslint-env jest */
import {
makeGetDataSourcesWithNodeTypeOptions,
getNetworkOptions,
makeGetExternalDataPropertyOptions,
} from '../selectors';


const mockState = {
protocol: {
present: {
externalData: {
assetManifest: {
foo: {
nodes: [
{ type: 'bar', attributes: { alpha: 1, bravo: 2 } },
{ type: 'bar', attributes: { charlie: 3, bravo: 2 } },
],
type: 'network',
name: 'My Network',
source: 'myNetwork.json',
},
sourceWithMixedNodes: {
nodes: [
{ type: 'something', attributes: { alpha: 1, bravo: 2 } },
{ type: 'else', attributes: { charlie: 3, bravo: 2 } },
],
bar: {
type: 'image',
name: 'An Image',
source: 'anImage.jpg',
},
},
variableRegistry: {
Expand All @@ -38,22 +36,10 @@ const mockState = {
};

describe('NameGeneratorListPrompts selectors', () => {
describe('getDataSourcesWithNodeTypeOptions()', () => {
let getDataSourcesWithNodeTypeOptions;

beforeEach(() => {
getDataSourcesWithNodeTypeOptions = makeGetDataSourcesWithNodeTypeOptions();
});

it('extracts dataSource properties into options list', () => {
const nodeType = 'something';

const mockProps = {
nodeType,
};

expect(getDataSourcesWithNodeTypeOptions(mockState, mockProps)).toEqual([
{ value: 'sourceWithMixedNodes', label: 'sourceWithMixedNodes' },
describe('getNetworkOptions()', () => {
it('extracts assetManifest networks into options list', () => {
expect(getNetworkOptions(mockState)).toEqual([
{ value: 'foo', label: 'My Network' },
]);
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,28 @@
import { createSelector } from 'reselect';
import { get, uniq, map, mapValues, reduce } from 'lodash';
import { getExternalData, getVariableRegistry } from '../../../../selectors/protocol';
import { get, map, reduce } from 'lodash';
import { getNetworkAssets, getVariableRegistry } from '../../../../selectors/protocol';
import { LABEL_VARIABLE_TYPES } from '../../../../config';

const getUniqueTypes = data =>
uniq(map(data, 'type'));

const getNodeType = (_, props) => props.nodeType;

/**
* Create an index of types available in each data source
*/
const getTypesBySource = createSelector(
getExternalData,
getVariableRegistry,
(externalData) => {
const typesBySource = mapValues(
externalData,
data => new Set(getUniqueTypes(data.nodes)),
);

return typesBySource;
},
);

/**
* Return a list of options for the current props.nodeType
*/
const makeGetDataSourcesWithNodeTypeOptions = () =>
createSelector(
getTypesBySource,
getNodeType,
(typesBySource, nodeType) => reduce(
typesBySource,
(acc, source, name) => {
if (!source.has(nodeType)) { return acc; }
return [
...acc,
{ label: name, value: name },
];
},
[],
),
const getNetworkOptions = (state) => {
const networkAssets = getNetworkAssets(state);

return map(
networkAssets,
(asset, name) => ({
label: asset.name,
value: name,
}),
);
};

/**
* Extracts unique variables used in `dataSource`, and combines them with the registry to
* create list of options in the format: `[ { value, label }, ...]`
* Create list of options for attributes from the variable registry in
* the format: `[ { value, label }, ...]`
*/
const makeGetExternalDataPropertyOptions = () =>
createSelector(
Expand Down Expand Up @@ -80,6 +56,6 @@ const makeGetExternalDataPropertyOptions = () =>
);

export {
makeGetDataSourcesWithNodeTypeOptions,
getNetworkOptions,
makeGetExternalDataPropertyOptions,
};
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { connect } from 'react-redux';
import {
getNetworkOptions,
makeGetExternalDataPropertyOptions,
makeGetDataSourcesWithNodeTypeOptions,
} from './selectors';

/**
* Provides `externalDataPropertyOptions`, and `dataSources` options ({ label, value }) props
*/
const makeMapStateToProps = () => {
const getExternalDataPropertyOptions = makeGetExternalDataPropertyOptions();
const getDataSourcesWithNodeTypeOptions = makeGetDataSourcesWithNodeTypeOptions();

const mapStateToProps = (state, props) => ({
dataSources: getDataSourcesWithNodeTypeOptions(state, props),
dataSources: getNetworkOptions(state),
externalDataPropertyOptions: getExternalDataPropertyOptions(state, props),
});

Expand Down
14 changes: 4 additions & 10 deletions src/components/StageEditor/sections/NodePanels/NodePanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,10 @@ import ValidatedField from '../../../Form/ValidatedField';
import { Item } from '../../../OrderedList';
import { getFieldId } from '../../../../utils/issues';

const getDataSourceOptions = (dataSources) => {
const externalData = dataSources.map(dataSource => (
{ value: dataSource, label: dataSource }
));

return ([
{ value: 'existing', label: 'Current network' },
...externalData,
]);
};
const getDataSourceOptions = dataSources => ([
{ value: 'existing', label: 'Current network' },
...dataSources,
]);

const NodePanel = ({ fieldId, dataSources, ...rest }) => (
<Item {...rest}>
Expand Down
10 changes: 3 additions & 7 deletions src/components/StageEditor/sections/NodePanels/NodePanels.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { connect } from 'react-redux';
import { FieldArray, arrayPush } from 'redux-form';
import uuid from 'uuid';
import cx from 'classnames';
import { keys, has, get } from 'lodash';
import { has } from 'lodash';
import Guidance from '../../../Guidance';
import OrderedList, { NewButton } from '../../../OrderedList';
import { getNetworkOptions } from '../NameGeneratorListPrompts/selectors';
import NodePanel from './NodePanel';

const NodePanels = ({ form, createNewPanel, dataSources, disabled, panels }) => {
Expand Down Expand Up @@ -54,14 +55,9 @@ NodePanels.defaultProps = {
panels: [],
};

const getDataSources = (state) => {
const externalData = get(state, 'protocol.present.externalData', {});
return keys(externalData);
};

const mapStateToProps = (state, props) => ({
disabled: !has(props.form.getValues(state, 'subject'), 'type'),
dataSources: getDataSources(state),
dataSources: getNetworkOptions(state),
panels: props.form.getValues(state, 'panels'),
});

Expand Down
24 changes: 15 additions & 9 deletions src/selectors/protocol.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const activeProtocolId = state => state.session.activeProtocol;
const protocolsMeta = state => state.protocols;

export const getProtocol = state => state.protocol.present;
export const getExternalData = state => state.protocol.present.externalData;
export const getAssetManifest = state => state.protocol.present.assetManifest;
export const getVariableRegistry = state => state.protocol.present.variableRegistry;

export const getActiveProtocolMeta = createSelector(
Expand All @@ -24,17 +24,23 @@ export const makeGetStage = () =>
(protocol, stageId) => find(protocol.stages, ['id', stageId]),
);

export const getExternalDataSources = createSelector(
getExternalData,
externalData =>
const networkTypes = new Set([
'network',
'async:network',
]);

// TODO: Does this method make sense here?
export const getNetworkAssets = createSelector(
getAssetManifest,
assetManifest =>
reduce(
externalData,
(memo, dataSource, name) => {
if (!Object.prototype.hasOwnProperty.call(dataSource, 'nodes')) { return memo; }
assetManifest,
(memo, asset, name) => {
if (!networkTypes.has(asset.type)) { return memo; }

return [...memo, name];
return { ...memo, [name]: asset };
},
[],
{},
),
);

48 changes: 48 additions & 0 deletions src/utils/__tests__/getAssetData.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* eslint-env jest */
import fs from 'fs';
import getAssetData from '../getAssetData';

const mockData = {
nodes: [],
edges: [],
};

fs.readFile = jest.fn(
(path, format, resolve) =>
resolve(null, JSON.stringify(mockData)),
);

describe('getAssetData', () => {
it('can load a json network', (done) => {
const source = '/dev/null/myMockSource.json';
const type = 'network';

getAssetData(source, type).then(
(data) => {
expect(data).toEqual(mockData);

done();
},
);
});

it('it caches responses', (done) => {
const source = '/dev/null/myMockSource.json';
const type = 'network';

Promise.all([
getAssetData(source, type),
getAssetData(source, type),
]).then(
(results) => {
const isSameObject = results.every(
(result, index, all) => result === all[0],
);

expect(isSameObject).toBe(true);

done();
},
);
});
});
Loading

0 comments on commit e7697b0

Please sign in to comment.