Skip to content

Commit

Permalink
Merge pull request #965 from codaco/feature/stage-node-filtering
Browse files Browse the repository at this point in the history
Schema v2
  • Loading branch information
jthrilly authored Oct 14, 2019
2 parents 3022ebc + 18e1305 commit 01e5e78
Show file tree
Hide file tree
Showing 22 changed files with 267 additions and 73 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion integration-tests/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import electron from 'electron';
import fs from 'fs-extra';
import { kebabCase } from 'lodash';
import paths from '../config/paths';
import { DEVELOPMENT_PROTOCOL_URL } from '../src/config';

// in ms
export const timing = {
Expand All @@ -12,7 +13,7 @@ export const timing = {
};

export const developmentProtocol = process.env.DEVELOPMENT_PROTOCOL ||
'https://github.com/codaco/development-protocol/releases/download/20190529123247-7c1e58a/development-protocol.netcanvas';
DEVELOPMENT_PROTOCOL_URL;

export const defaultImageSnaphotConfig = {
// { testPath, currentTestName, counter, defaultIdentifier }
Expand Down
5 changes: 5 additions & 0 deletions src/components/Canvas/ConcentricCircles.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const ConcentricCircles = ({
connectFrom,
updateLinkFrom,
className,
stage,
}) => (
<Canvas className={cx('concentric-circles', className)} id="concentric-circles">
<Background
Expand All @@ -45,6 +46,7 @@ const ConcentricCircles = ({
displayEdges={displayEdges}
subject={subject}
layoutVariable={layoutVariable}
stage={stage}
/>
}
<NodeLayout
Expand All @@ -57,18 +59,21 @@ const ConcentricCircles = ({
subject={subject}
connectFrom={connectFrom}
updateLinkFrom={updateLinkFrom}
stage={stage}
/>
<NodeBucket
id="NODE_BUCKET"
layoutVariable={layoutVariable}
subject={subject}
sortOrder={sortOrder}
stage={stage}
/>
</Canvas>
);

ConcentricCircles.propTypes = {
subject: PropTypes.object.isRequired,
stage: PropTypes.object.isRequired,
layoutVariable: PropTypes.string.isRequired,
highlightAttribute: PropTypes.string,
allowHighlighting: PropTypes.bool,
Expand Down
3 changes: 2 additions & 1 deletion src/components/MainMenu/SettingsMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Icon, Button } from '../../ui/components';
import Scroller from '../Scroller';
import { Toggle, Text } from '../../ui/components/Fields';
import MenuPanel from './MenuPanel';
import { DEVELOPMENT_PROTOCOL_URL } from '../../config';

class SettingsMenu extends PureComponent {
constructor(props) {
Expand Down Expand Up @@ -206,7 +207,7 @@ class SettingsMenu extends PureComponent {
<section>
<Button
color="mustard"
onClick={() => importProtocolFromURI('https://github.com/codaco/development-protocol/releases/download/20190529123247-7c1e58a/development-protocol.netcanvas')}
onClick={() => importProtocolFromURI(DEVELOPMENT_PROTOCOL_URL)}
>
Import development protocol
</Button>
Expand Down
158 changes: 138 additions & 20 deletions src/components/Setup/ProtocolCard.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,136 @@
import React from 'react';
import PropTypes from 'prop-types';

import { connect } from 'react-redux';
import cx from 'classnames';
import { actionCreators as dialogActions } from '../../ducks/modules/dialogs';
import { actionCreators as installedProtocolsActions } from '../../ducks/modules/installedProtocols';
import { Icon } from '../../ui/components';
import { APP_SUPPORTED_SCHEMA_VERSIONS, APP_SCHEMA_VERSION } from '../../config';

class ProtocolCard extends React.Component {
isOutdatedProtocol = () =>
this.props.protocol.schemaVersion !== APP_SCHEMA_VERSION &&
APP_SUPPORTED_SCHEMA_VERSIONS.includes(this.props.protocol.schemaVersion);

isObsoleteProtocol = () => false; // To be implemented in future, as needed.

const ProtocolCard = ({ protocol, selectProtocol, onDelete }) => {
const handleDelete = (e) => {
e.preventDefault();
e.stopPropagation();
onDelete();
handleDeleteProtocol = () => {
this.props.deleteProtocol(this.props.protocol.uuid);
};

return (
<div className="protocol-card" onClick={() => selectProtocol(protocol)}>
<div className="protocol-card__delete" onClick={handleDelete}>
<Icon name="delete" />
</div>
<h2 className="protocol-card__name">{protocol.name}</h2>
<div className="protocol-card__icon-wrapper">
<Icon className="protocol-card__icon" name="protocol-card" />
</div>
{ protocol.description ? (<p className="protocol-card__description">{protocol.description}</p>) : ''}
</div>
handleSchemaOutdatedInfo = () => {
this.props.openDialog({
type: 'Warning',
title: 'Out of Date Schema',
canCancel: false,
message: (
<React.Fragment>
<p>
This protocol uses an older version of the protocol file format, or &quot;schema&quot;.
</p>
<p>
Newer schema versions support additional features in Network Canvas, and may be
required in order to use this protocol in the future. To avoid losing the ability
to conduct interviews, you are strongly advised to migrate this protocol to the
latest schema version. To do this, open the protocol file it in the latest version
of Architect, and follow the migration instructions. Once migrated, install the
new version of the protocol on this device.
</p>
<p>
For documentation on this issue, please see our documentation site.
</p>
<p>
In the meantime, you can continue to use this protocol to start interviews or
export data.
</p>
</React.Fragment>
),
});
};

handleSchemaObsoleteInfo = () => {
this.props.openDialog({
type: 'Error',
title: 'Obsolete Protocol Schema',
canCancel: false,
message: (
<React.Fragment>
<p>
This protocol uses an obsolete version of the protocol file format, or
&quot;schema&quot;.
</p>
<p>
The version of the schema used by this protocol is incompatible with this version of
Network Canvas. You may still export interview data that has already been collected,
but you may not start additional interviews.
</p>
<p>
If you require the ability to start interviews, you can either (1) install an updated
version of this protocol that uses the latest schema, or (2) downgrade your version
of Network Canvas to a version that supports this protocol schema version.
</p>
<p>
For documentation on this issue, please see our documentation site.
</p>
</React.Fragment>
),
});
};

modifierClasses = cx(
'protocol-card',
{ 'protocol-card--warning': !this.isObsoleteProtocol() && this.isOutdatedProtocol() },
{ 'protocol-card--error': this.isObsoleteProtocol() },
);
};

startButtonClasses = this.isObsoleteProtocol() ? ('start-button start-button--disabled') : ('start-button');

renderCardIcon() {
if (this.isOutdatedProtocol()) {
return (
<div className="protocol-card__warning" onClick={this.handleSchemaOutdatedInfo}>
<Icon name="warning" />
</div>
);
}

if (this.isObsoleteProtocol()) {
return (
<div className="protocol-card__error" onClick={this.handleSchemaObsoleteInfo}>
<Icon name="error" />
</div>
);
}

return ('');
}

render() {
const { protocol, selectProtocol } = this.props;

return (
<div className={this.modifierClasses}>
<div className="protocol-card__delete" onClick={this.handleDeleteProtocol}>
<Icon name="delete" />
</div>
{this.renderCardIcon()}
<h2 className="protocol-card__name">{protocol.name}</h2>
<p className="protocol-card__description">
{ protocol.description ?
protocol.description : (<em>No protocol description.</em>)}
</p>
<div
className={this.startButtonClasses}
onClick={() => selectProtocol(protocol)}
>
<Icon className="start-button__protocol-icon" name="protocol-card" />
<div className="start-button__text">Start new interview</div>
<Icon className="start-button__arrow" name="chevron-right" />
</div>
</div>
);
}
}

ProtocolCard.defaultProps = {
className: '',
Expand All @@ -32,11 +140,21 @@ ProtocolCard.defaultProps = {

ProtocolCard.propTypes = {
selectProtocol: PropTypes.func,
onDelete: PropTypes.func.isRequired,
openDialog: PropTypes.func.isRequired,
deleteProtocol: PropTypes.func.isRequired,
protocol: PropTypes.shape({
uuid: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
description: PropTypes.string,
schemaVersion: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
}).isRequired,
};

export default ProtocolCard;
const mapDispatchToProps = {
openDialog: dialogActions.openDialog,
deleteProtocol: installedProtocolsActions.deleteProtocol,
};

export { ProtocolCard as UnconnectedProtocolCard };

export default connect(null, mapDispatchToProps)(ProtocolCard);
22 changes: 9 additions & 13 deletions src/components/Setup/__tests__/ProtocolCard.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import React from 'react';
import { shallow } from 'enzyme';

import ProtocolCard from '../ProtocolCard';
import { UnconnectedProtocolCard as ProtocolCard } from '../ProtocolCard';

describe('<ProtocolCard>', () => {
let component;
Expand All @@ -13,34 +13,30 @@ describe('<ProtocolCard>', () => {
beforeEach(() => {
selectHandler = jest.fn();
deleteHandler = jest.fn();
mockProtocol = { name: 'my-mock-protocol', description: '1.0.1' };
mockProtocol = { name: 'My Mock Protocol', description: 'Protocol description', schemaVersion: 2 };
component = shallow((
<ProtocolCard
selectProtocol={selectHandler}
onDelete={deleteHandler}
protocol={mockProtocol}
deleteProtocol={deleteHandler}
/>
));
});

it('renders an icon', () => {
// close button and card icon
expect(component.find('Icon')).toHaveLength(2);
});

it('renders name & description', () => {
expect(component.text()).toMatch(mockProtocol.name);
expect(component.text()).toMatch(mockProtocol.description);
});

it('downloads on click', () => {
component.simulate('click');
expect(selectHandler).toHaveBeenCalledTimes(1);
});

it('deletes on click delete', () => {
const mockEvent = { stopPropagation: () => {}, preventDefault: () => {} };
component.find('.protocol-card__delete').simulate('click', mockEvent);
expect(deleteHandler).toHaveBeenCalledTimes(1);
});

it('starts a session when button is clicked', () => {
const mockEvent = { stopPropagation: () => {}, preventDefault: () => {} };
component.find('.start-button').simulate('click', mockEvent);
expect(selectHandler).toHaveBeenCalledTimes(1);
});
});
8 changes: 8 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* eslint-disable import/prefer-default-export */

// Target protocol schema version. Used to determine compatibility & migration
export const APP_SCHEMA_VERSION = 2;

export const APP_SUPPORTED_SCHEMA_VERSIONS = ['1.0.0', 1, 2];

export const DEVELOPMENT_PROTOCOL_URL = 'https://github.com/codaco/development-protocol/releases/download/20191011105845-fdad2bc/Development.netcanvas';
4 changes: 2 additions & 2 deletions src/containers/Canvas/EdgeLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { makeGetDisplayEdges } from '../../selectors/canvas';
const makeMapStateToProps = () => {
const getDisplayEdges = makeGetDisplayEdges();

const mapStateToProps = (state, { subject, displayEdges, layoutVariable }) => ({
edges: getDisplayEdges(state, { subject, displayEdges, layoutVariable }),
const mapStateToProps = (state, { subject, displayEdges, layoutVariable, stage }) => ({
edges: getDisplayEdges(state, { subject, displayEdges, layoutVariable, stage }),
});

return mapStateToProps;
Expand Down
4 changes: 2 additions & 2 deletions src/containers/Canvas/NodeBucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ class NodeBucket extends PureComponent {
const makeMapStateToProps = () => {
const getNextUnplacedNode = makeGetNextUnplacedNode();

const mapStateToProps = (state, { layoutVariable, subject, sortOrder }) => {
const node = getNextUnplacedNode(state, { layoutVariable, subject, sortOrder });
const mapStateToProps = (state, { layoutVariable, subject, sortOrder, stage }) => {
const node = getNextUnplacedNode(state, { layoutVariable, subject, sortOrder, stage });

return {
node,
Expand Down
4 changes: 2 additions & 2 deletions src/containers/Canvas/NodeLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,12 @@ function makeMapStateToProps() {

return function mapStateToProps(
state,
{ createEdge, allowHighlighting, subject, layoutVariable },
{ createEdge, allowHighlighting, subject, layoutVariable, stage },
) {
const allowSelect = !!(createEdge || allowHighlighting);

return {
nodes: getPlacedNodes(state, { subject, layoutVariable }),
nodes: getPlacedNodes(state, { subject, layoutVariable, stage }),
allowSelect,
};
};
Expand Down
2 changes: 1 addition & 1 deletion src/containers/Interfaces/OrdinalBin.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function makeMapStateToProps() {
const getStageNodes = makeNetworkNodesForType();
const getPromptVariable = makeGetPromptVariable();

return function mapStateToProps(state, props) {
return (state, props) => {
const stageNodes = getStageNodes(state, props);
const activePromptVariable = getPromptVariable(state, props);

Expand Down
1 change: 1 addition & 0 deletions src/containers/Interfaces/Sociogram.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const Sociogram = ({
connectFrom={connectFrom}
updateLinkFrom={handleConnectFrom}
key={prompt.id}
stage={stage}
/>
</div>
<div style={{ position: 'absolute', right: '3rem', bottom: '3rem' }}>
Expand Down
Loading

0 comments on commit 01e5e78

Please sign in to comment.