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

[KED-1650] New graphing algorithm & feature flags #185

Merged
merged 43 commits into from
Jun 23, 2020
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
a6fd8e0
Remove unnecessary null props
richardwestenra Apr 30, 2020
00bf444
Don't use a button for node-list-row when !onClick
richardwestenra Apr 30, 2020
61f470a
Remove unused modifier classes
richardwestenra Apr 30, 2020
52012c1
Add --button modifier class for row hover state
richardwestenra Apr 30, 2020
f65734a
Combine duplicate classes and remove unused
richardwestenra Apr 30, 2020
4dadbe6
Add new selected node state
richardwestenra Apr 30, 2020
62ffcca
Improve consistency of row transitions
richardwestenra Apr 30, 2020
3414bad
Prevent a node being selected when disabled
richardwestenra Apr 30, 2020
be6ac2c
Remove node clicked state when disabled
richardwestenra Apr 30, 2020
9cde1f9
Fix node-list-row button styling
richardwestenra Apr 30, 2020
9eaacd7
Use disabled button instead of text
richardwestenra Apr 30, 2020
6cae100
Fix icon opacity when disabled
richardwestenra Apr 30, 2020
91a14bf
Fix tests
richardwestenra Apr 30, 2020
9eff809
Add a new test for TOGGLE_NODES_DISABLED reducer
richardwestenra Apr 30, 2020
4f99a6d
Fix node-list hover/selected bg colours
richardwestenra May 1, 2020
899c97f
[KED-989] Added new graphing approach concept
bru5 May 6, 2020
d0ed892
[KED-1650] Refactor and tidy graph code
bru5 May 7, 2020
2791c80
Merge branch 'develop' into feature/graph-concept
bru5 May 12, 2020
3aba5d3
[KED-1650] Improve graph concept
bru5 May 14, 2020
3a44d15
[KED-1650] Refactor graph concept
bru5 May 18, 2020
bceb5fc
[KED-1650] Refactor graph concept
bru5 May 19, 2020
4f85def
[KED-1698] Add layers to graph concept and improve layout
bru5 May 22, 2020
788bcba
[KED-1697] Added feature flags with flag for graph concept
bru5 May 22, 2020
09964c1
Merge branch 'develop' into feature/graph-concept
bru5 May 22, 2020
748aa94
[KED-1650] Refactoring, add comments and docs
bru5 May 27, 2020
2c711e2
[KED-1650] Added tests and some refactoring
bru5 Jun 1, 2020
a9eba5c
Merge branch 'develop' into feature/graph-concept
bru5 Jun 4, 2020
4ecf994
[KED-1650] Fix exception object
bru5 Jun 4, 2020
29eba7d
[KED-1698] Fix layer constraints
bru5 Jun 16, 2020
da956ef
[KED-1650] Change flags URL format
bru5 Jun 16, 2020
0ad21eb
[KED-1650] Refactor and simplify
bru5 Jun 16, 2020
e170d88
[KED-1650] Improve code clarity
bru5 Jun 16, 2020
671938b
[KED-1650] Add empty value for flags
bru5 Jun 17, 2020
83bfe09
[KED-1650] Improve routing
bru5 Jun 18, 2020
95a1e15
[KED-1698] Fix layer solving
bru5 Jun 19, 2020
3fac281
[KED-1650] Update readme with flags
bru5 Jun 19, 2020
c154833
[KED-1650] Update license info
bru5 Jun 19, 2020
b38bb3f
Merge branch 'develop' into feature/graph-concept
bru5 Jun 19, 2020
d1d12dd
[KED-1650] Update readme
bru5 Jun 22, 2020
c6c66c6
[KED-1650] Update license info
bru5 Jun 22, 2020
f92d68f
[KED-1650] Update license info
bru5 Jun 23, 2020
75c6302
[KED-1650] Update readme
bru5 Jun 23, 2020
5b8f4e5
[KED-1650] Improve clarity
bru5 Jun 23, 2020
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
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"d3-transition": "^1.2.0",
"d3-zoom": "^1.7.3",
"dagre": "git+https://github.com/richardwestenra/dagre.git#manual-ranking",
"kiwi.js": "^1.1.2",
"konami-code": "^0.2.1",
"react-custom-scrollbars": "^4.2.1",
"react-flip-toolkit": "^7.0.7",
Expand Down
11 changes: 11 additions & 0 deletions src/actions/actions.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import animals from '../utils/data/animals.mock';
import {
CHANGE_FLAG,
RESET_DATA,
TOGGLE_LAYERS,
TOGGLE_EXPORT_MODAL,
Expand All @@ -8,6 +9,7 @@ import {
TOGGLE_THEME,
UPDATE_CHART_SIZE,
UPDATE_FONT_LOADED,
changeFlag,
resetData,
toggleLayers,
toggleExportModal,
Expand Down Expand Up @@ -174,4 +176,13 @@ describe('actions', () => {
};
expect(updateFontLoaded(fontLoaded)).toEqual(expectedAction);
});

it('should create an action to change a flag', () => {
const expectedAction = {
type: CHANGE_FLAG,
name: 'testFlag',
value: true
};
expect(changeFlag('testFlag', true)).toEqual(expectedAction);
});
});
15 changes: 15 additions & 0 deletions src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,18 @@ export function updateFontLoaded(fontLoaded) {
fontLoaded
};
}

export const CHANGE_FLAG = 'CHANGE_FLAG';

/**
* Change the given feature flag
* @param {string} name The flag name
* @param {value} value The value to set
*/
export function changeFlag(name, value) {
return {
type: CHANGE_FLAG,
name,
value
};
}
9 changes: 9 additions & 0 deletions src/components/app/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import App from './index';
import getRandomPipeline from '../../utils/random-data';
import animals from '../../utils/data/animals.mock';
import loremIpsum from '../../utils/data/lorem-ipsum.mock';
import { Flags } from '../../utils/flags';

describe('App', () => {
describe('renders without crashing', () => {
Expand Down Expand Up @@ -35,6 +36,14 @@ describe('App', () => {
});
});

describe('feature flags', () => {
it('it announces flags', () => {
const announceFlags = jest.spyOn(App.prototype, 'announceFlags');
shallow(<App data={loremIpsum} />);
expect(announceFlags).toHaveBeenCalledWith(Flags.defaults());
});
});

describe('throws an error', () => {
it('when data prop is empty', () => {
expect(() => shallow(<App />)).toThrow();
Expand Down
13 changes: 13 additions & 0 deletions src/components/app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Wrapper from '../wrapper';
import getInitialState from '../../store/initial-state';
import loadData from '../../store/load-data';
import normalizeData from '../../store/normalize-data';
import { getFlagsMessage } from '../../utils/flags';
import '@quantumblack/kedro-ui/lib/styles/app.css';
import './app.css';

Expand All @@ -19,6 +20,7 @@ class App extends React.Component {
super(props);
const initialState = getInitialState(props);
this.store = configureStore(initialState);
this.announceFlags(initialState.flags);
}

componentDidMount() {
Expand All @@ -32,6 +34,17 @@ class App extends React.Component {
}
}

/**
* Shows a console message regarding the given flags
*/
announceFlags(flags) {
const message = getFlagsMessage(flags);

if (message && typeof jest === 'undefined') {
console.info(message);
}
}

/**
* Load data asynchronously from a JSON file then update the store
*/
Expand Down
8 changes: 8 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,11 @@ export const sidebarWidth = {
open: 400,
closed: 60
};

export const flags = {
newgraph: {
description: 'Improved graphing algorithm',
default: false,
icon: '📈'
}
};
16 changes: 16 additions & 0 deletions src/reducers/flags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { CHANGE_FLAG } from '../actions';

function flagsReducer(flagsState = {}, action) {
switch (action.type) {
case CHANGE_FLAG: {
return Object.assign({}, flagsState, {
[action.name]: action.value
});
}

default:
return flagsState;
}
}

export default flagsReducer;
2 changes: 2 additions & 0 deletions src/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import node from './nodes';
import tag from './tags';
import nodeType from './node-type';
import visible from './visible';
import flags from './flags';
import {
RESET_DATA,
TOGGLE_TEXT_LABELS,
Expand Down Expand Up @@ -46,6 +47,7 @@ const combinedReducer = combineReducers({
nodeType,
tag,
visible,
flags,
edge: (state = {}) => state,
id: (state = null) => state,
layer: (state = {}) => state,
Expand Down
12 changes: 12 additions & 0 deletions src/reducers/reducers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { mockState } from '../utils/state.mock';
import reducer from './index';
import normalizeData from '../store/normalize-data';
import {
CHANGE_FLAG,
RESET_DATA,
TOGGLE_LAYERS,
TOGGLE_SIDEBAR,
Expand Down Expand Up @@ -194,4 +195,15 @@ describe('Reducer', () => {
expect(newState.fontLoaded).toBe(true);
});
});

describe('CHANGE_FLAG', () => {
it('should update the state when a flag is changed', () => {
const newState = reducer(mockState.lorem, {
type: CHANGE_FLAG,
name: 'testFlag',
value: true
});
expect(newState.flags.testFlag).toBe(true);
});
});
});
11 changes: 11 additions & 0 deletions src/selectors/flags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createSelector } from 'reselect';

const getFlagsState = state => state.flags;

/**
* Get current flag status from state
*/
export const getCurrentFlags = createSelector(
[getFlagsState],
flags => ({ ...flags })
);
10 changes: 10 additions & 0 deletions src/selectors/flags.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { getCurrentFlags } from './flags';

describe('getCurrentFlags function', () => {
it('should return the current flags from state', () => {
const flags = getCurrentFlags({
flags: { mockFlagA: true, mockFlagB: false }
});
expect(flags).toEqual({ mockFlagA: true, mockFlagB: false });
});
});
48 changes: 21 additions & 27 deletions src/selectors/layers.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,35 @@ const getLayerName = state => state.layer.name;
export const getLayers = createSelector(
[getLayoutNodes, getVisibleLayerIDs, getLayerName, getGraphSize],
(nodes, layerIDs, layerName, { width }) => {
// Get list of layer Y positions from nodes
const layerY = nodes.reduce((layerY, node) => {
if (!layerY[node.layer]) {
layerY[node.layer] = [];
}
layerY[node.layer].push(node.y);
return layerY;
}, {});
const bounds = {};

/**
* Determine the y position and height of a layer band
* @param {number} id
*/
const calculateYPos = (layerID, prevID, nextID) => {
const yMin = Math.min(...layerY[layerID]);
const yMax = Math.max(...layerY[layerID]);
const prev = layerY[prevID];
const next = layerY[nextID];
const topYGap = prev && yMin - Math.max(...prev);
const bottomYGap = next && Math.min(...next) - yMax;
const yGap = (topYGap || bottomYGap) / 2;
const y = yMin - yGap;
const height = yMax + yGap - y;
return { y, height };
};
for (const node of nodes) {
const bound =
bounds[node.layer] || (bounds[node.layer] = [Infinity, -Infinity]);
if (node.y - node.height < bound[0]) bound[0] = node.y - node.height;
if (node.y + node.height > bound[1]) bound[1] = node.y + node.height;
}

return layerIDs.map((id, i) => {
const prevID = layerIDs[i - 1];
const nextID = layerIDs[i + 1];
const currentBound = bounds[id];
const prevBound = bounds[layerIDs[i - 1]] || [
currentBound[0],
currentBound[0]
];
const nextBound = bounds[layerIDs[i + 1]] || [
currentBound[1],
currentBound[1]
];
const start = (prevBound[1] + currentBound[0]) / 2;
const end = (currentBound[1] + nextBound[0]) / 2;

return {
id,
name: layerName[id],
x: -width / 2,
y: start,
width: width * 2,
...calculateYPos(id, prevID, nextID)
height: end - start
};
});
}
Expand Down
42 changes: 24 additions & 18 deletions src/selectors/layers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,30 @@ describe('Selectors', () => {
return layers;
}, {});

expect(
nodes.every(node => {
// we don't need to check y/height positions if the layer isn't there.
if (node.layer === null) {
return true;
}
const i = layerIDs.indexOf(node.layer);
const prevLayer = layersObj[layerIDs[i - 1]];
const thisLayer = layersObj[node.layer];
const nextLayer = layersObj[layerIDs[i + 1]];
return (
(!prevLayer || node.y > prevLayer.y + prevLayer.height) &&
node.y > thisLayer.y &&
node.y + node.height < thisLayer.y + thisLayer.height &&
(!nextLayer || node.y + node.height < nextLayer.y)
);
})
).toBe(true);
nodes.forEach(node => {
// we don't need to check y/height positions if the layer isn't there.
if (node.layer === null) {
return;
}

const i = layerIDs.indexOf(node.layer);
const prevLayer = layersObj[layerIDs[i - 1]];
const thisLayer = layersObj[node.layer];
const nextLayer = layersObj[layerIDs[i + 1]];

if (prevLayer) {
expect(node.y).toBeGreaterThanOrEqual(prevLayer.y + prevLayer.height);
}

expect(node.y).toBeGreaterThanOrEqual(thisLayer.y);
expect(node.y + node.height).toBeLessThanOrEqual(
thisLayer.y + thisLayer.height
);

if (nextLayer) {
expect(node.y + node.height).toBeLessThanOrEqual(nextLayer.y);
}
});
});
});
});
Loading