Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

Commit

Permalink
Merge pull request #229 from codaco/feature/sortable-panels
Browse files Browse the repository at this point in the history
Sortable panels
  • Loading branch information
jthrilly committed Jan 18, 2019
2 parents 2ae152a + 7c8ed3b commit 259102d
Show file tree
Hide file tree
Showing 50 changed files with 1,093 additions and 667 deletions.
1 change: 1 addition & 0 deletions src/main/server/__tests__/AdminService-test.js
Expand Up @@ -6,6 +6,7 @@ const { jsonClient, makeUrl } = require('../../../../config/jest/setupTestEnv');
const DeviceManager = require('../../data-managers/DeviceManager');
const ProtocolManager = require('../../data-managers/ProtocolManager');

jest.mock('nedb');
jest.mock('electron-log');
jest.mock('../../data-managers/DeviceManager');
jest.mock('../../data-managers/ProtocolManager');
Expand Down
2 changes: 1 addition & 1 deletion src/main/server/__tests__/apiRequestLogger-test.js
Expand Up @@ -11,6 +11,6 @@ const mockNext = jest.fn();
describe('API request logger', () => {
it('logs requests as info', () => {
apiRequestLogger()(mockReq, mockRes, mockNext);
expect(logger.info).toHaveBeenCalled();
expect(logger.debug).toHaveBeenCalled();
});
});
2 changes: 1 addition & 1 deletion src/main/server/apiRequestLogger.js
Expand Up @@ -8,7 +8,7 @@ const format = (req, res, tag = 'API') => (
* Logging plugin to be used on restify's `after` event
*/
const apiRequestLogger = tag => (req, res/* , route, err */) => {
logger.info(format(req, res, tag));
logger.debug(format(req, res, tag));
};

module.exports = apiRequestLogger;
13 changes: 8 additions & 5 deletions src/renderer/components/index.js
@@ -1,3 +1,4 @@
export { default as AnswerDistributionPanel } from './workspace/AnswerDistributionPanel';
export { default as AppMessage } from './AppMessage';
export { default as BarChart } from './charts/BarChart';
export { default as CountsWidget } from './charts/CountsWidget';
Expand All @@ -11,15 +12,17 @@ export { default as InterviewWidget } from './charts/InterviewWidget';
export { default as TimeSeriesChart } from './charts/TimeSeriesChart';
export { default as Modal } from './Modal';
export { default as Overflow } from './Overflow';
export { default as PanelItem } from './PanelItem';
export { default as PanelItem } from './workspace/PanelItem';
export { default as PairPrompt } from './pairing/PairPrompt';
export { default as PairPin } from './pairing/PairPin';
export { default as PairedDeviceModal } from './PairedDeviceModal';
export { default as PieChart } from './charts/PieChart';
export { default as ProtocolPanel } from './ProtocolPanel';
export { default as ProtocolPanel } from './workspace/ProtocolPanel';
export { default as Scrollable } from './Scrollable';
export { default as ScrollingPanelItem } from './ScrollingPanelItem';
export { default as ServerPanel } from './ServerPanel';
export { default as SessionHistoryPanel } from './SessionHistoryPanel';
export { default as SessionPanel } from './SessionPanel';
export { default as ServerPanel } from './workspace/ServerPanel';
export { default as SessionHistoryPanel } from './workspace/SessionHistoryPanel';
export { default as SessionPanel } from './workspace/SessionPanel';
export { default as SortablePanel } from './workspace/SortablePanel';
export { default as SortablePanels } from './workspace/SortablePanels';
export { default as TabBar } from './TabBar';
@@ -1,8 +1,8 @@
import React from 'react';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';

import Types from '../types';
import { BarChart, EmptyData, PieChart } from '../components';
import Types from '../../types';
import { BarChart, EmptyData, PieChart } from '../../components';

const sumValues = groups => groups.reduce((sum, group) => sum + group.value, 0);

Expand All @@ -22,28 +22,31 @@ const content = (chartData, variableType) => {
* Depending on variableType, renders either a pie chart with a distribution of categorical
* node attributes, or a Bar chart with a distribution of ordinal attributes.
*/
const AnswerDistributionPanel = ({ chartData, variableDefinition }) => {
const totalObservations = sumValues(chartData);
return (
<div className="dashboard__panel dashboard__panel--chart">
<h4 className="dashboard__header-text">
{variableDefinition.label}
<small className="dashboard__header-subtext">
{headerLabel(variableDefinition.type)} distribution
</small>
</h4>
<div className="dashboard__chartContainer">
{content(chartData, variableDefinition.type)}
class AnswerDistributionPanel extends PureComponent {
render() {
const { chartData, variableDefinition } = this.props;
const totalObservations = sumValues(chartData);
return (
<div className="dashboard__panel dashboard__panel--chart">
<h4 className="dashboard__header-text">
{variableDefinition.label}
<small className="dashboard__header-subtext">
{headerLabel(variableDefinition.type)} distribution
</small>
</h4>
<div className="dashboard__chartContainer">
{content(chartData, variableDefinition.type)}
</div>
<div className="dashboard__chartFooter">
{
totalObservations > 0 &&
`Total: ${totalObservations} observations`
}
</div>
</div>
<div className="dashboard__chartFooter">
{
totalObservations > 0 &&
`Total: ${totalObservations} observations`
}
</div>
</div>
);
};
);
}
}

AnswerDistributionPanel.defaultProps = {
chartData: [],
Expand Down
@@ -1,6 +1,6 @@
import React from 'react';

import Types from '../types';
import Types from '../../types';
import PanelItem from './PanelItem';

const ProtocolPanel = ({ protocol }) => (
Expand Down
@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { PanelItem } from '../components';
import withApiClient from './withApiClient';
import { PanelItem } from '../../components';
import withApiClient from '../withApiClient';

class ServerPanel extends Component {
constructor() {
Expand Down
@@ -1,8 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';

import BarChart from './charts/BarChart';
import EmptyData from './charts/EmptyData';
import BarChart from '../charts/BarChart';
import EmptyData from '../charts/EmptyData';

const buildChartContent = (sessions) => {
if (!sessions.length) {
Expand Down
@@ -1,8 +1,8 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';

import { DismissButton, ScrollingPanelItem } from '../components';
import { formatDate } from '../utils/formatters';
import { DismissButton, ScrollingPanelItem } from '../../components';
import { formatDate } from '../../utils/formatters';

const emptyContent = (<p>Interviews you import from Network Canvas will appear here.</p>);

Expand Down
23 changes: 23 additions & 0 deletions src/renderer/components/workspace/SortablePanel.js
@@ -0,0 +1,23 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { SortableElement, SortableHandle } from 'react-sortable-hoc';

const DragHandle = SortableHandle(() => <div className="sortable__handle" />);

class Panel extends PureComponent {
render() {
const { children } = this.props;
return (
<div className="sortable">
{ children }
<DragHandle />
</div>
);
}
}

Panel.propTypes = {
children: PropTypes.node.isRequired,
};

export default SortableElement(Panel);
42 changes: 42 additions & 0 deletions src/renderer/components/workspace/SortablePanels.js
@@ -0,0 +1,42 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { SortableContainer } from 'react-sortable-hoc';

import SortablePanel from './SortablePanel';

/**
* Render a collection of sortable workspace panels.
* Note: SortableContainer must render a containing DOM element (<div>) around the
* SortableElement collection.
*/
class Panels extends PureComponent {
render() {
const { className, panels } = this.props;
return (
<div className={className}>
{
panels.map((panel, index) => (
<SortablePanel
key={`${panel.key}-panel`}
index={index}
disabled={panel.props.disabled || false}
>
{ panel }
</SortablePanel>
))
}
</div>
);
}
}

Panels.defaultProps = {
className: '',
};

Panels.propTypes = {
className: PropTypes.string,
panels: PropTypes.arrayOf(PropTypes.node).isRequired,
};

export default SortableContainer(Panels, { withRef: true });
@@ -1,7 +1,7 @@
/* eslint-env jest */
import React from 'react';
import { shallow } from 'enzyme';
import { mockProtocol } from '../../../../config/jest/setupTestEnv';
import { mockProtocol } from '../../../../../config/jest/setupTestEnv';

import ProtocolPanel from '../ProtocolPanel';

Expand Down
24 changes: 24 additions & 0 deletions src/renderer/components/workspace/__tests__/SortablePanel-test.js
@@ -0,0 +1,24 @@
/* eslint-env jest */
import React from 'react';
import { mount, shallow } from 'enzyme';

import SortablePanel from '../SortablePanel';

jest.mock('react-sortable-hoc', () => ({
SortableElement: jest.fn(component => component),
SortableHandle: jest.fn(component => component),
}));

describe('SortablePanel', () => {
it('renders provided children', () => {
const children = <div>mock</div>;
const sortable = shallow(<SortablePanel index={0}>{children}</SortablePanel>);
expect(sortable.contains(children)).toBe(true);
});

it('renders a drag handle', () => {
const children = <div>mock</div>;
const sortable = mount(<SortablePanel index={0}>{children}</SortablePanel>);
expect(sortable.find('.sortable__handle')).toHaveLength(1);
});
});
26 changes: 26 additions & 0 deletions src/renderer/components/workspace/__tests__/SortablePanels-test.js
@@ -0,0 +1,26 @@
/* eslint-env jest */
import React from 'react';
import { shallow } from 'enzyme';

import SortablePanels from '../SortablePanels';

jest.mock('react-sortable-hoc', () => ({
SortableContainer: jest.fn(component => component),
SortableElement: jest.fn(component => component),
SortableHandle: jest.fn(component => component),
}));

describe('SortablePanel', () => {
it('renders children mapped from a "panels" prop', () => {
const panels = [<div>mock</div>];
const subject = shallow(<SortablePanels panels={panels} />);
expect(subject.contains(panels[0])).toBe(true);
});

it('defines an index for sorting', () => {
const panels = [<div>mock</div>, <div>mock</div>];
const subject = shallow(<SortablePanels panels={panels} />).find('Panel');
expect(subject.get(0).props.index).toBe(0);
expect(subject.get(1).props.index).toBe(1);
});
});

0 comments on commit 259102d

Please sign in to comment.