Skip to content

Commit

Permalink
feat: 🎸 properties tree
Browse files Browse the repository at this point in the history
  • Loading branch information
maciejrybaniec committed Jul 28, 2020
1 parent 83bae31 commit c64d00d
Show file tree
Hide file tree
Showing 33 changed files with 481 additions and 89 deletions.
18 changes: 4 additions & 14 deletions lib/js/app/KeenExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,6 @@ import { version } from '../../../package.json';
import App from './components/App';
import { AppContext } from './contexts';

const defaultConfig = {
previewCollection: true,
saveStateToLocalStorage: {
eventCollection: true,
},
};

export let client;
export let keenTrackingClient;

Expand Down Expand Up @@ -56,13 +49,10 @@ export class KeenExplorer {

ReactDOM.render(
<Provider store={store}>
<AppContext.Provider value={{ keenAnalysis: client }}>
<App
{...{
...defaultConfig,
...props,
}}
/>
<AppContext.Provider
value={{ keenAnalysis: client, modalContainer: props.modalContainer }}
>
<App {...props} />
</AppContext.Provider>
</Provider>,
document.querySelector(props.container)
Expand Down
2 changes: 2 additions & 0 deletions lib/js/app/components/Creator/Creator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type Props = {
const Creator: FC<Props> = ({ onUpdateQuery }) => {
const dispatch = useDispatch();
const {
modalContainer,
keenAnalysis: { config },
} = useContext(AppContext);

Expand All @@ -22,6 +23,7 @@ const Creator: FC<Props> = ({ onUpdateQuery }) => {

return (
<QueryCreator
modalContainer={modalContainer}
projectId={config.projectId}
readKey={config.readKey}
masterKey={config.masterKey}
Expand Down
2 changes: 2 additions & 0 deletions lib/js/app/contexts/AppContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import React from 'react';

const AppContext = React.createContext<{
keenAnalysis: any;
modalContainer: string;
}>({
keenAnalysis: null,
modalContainer: null,
});

export default AppContext;
9 changes: 8 additions & 1 deletion lib/js/app/queryCreator/QueryCreator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import KeenAnalysis from 'keen-analysis';
import App from './App';
import rootSaga from './saga';
import rootReducer from './reducer';
import { AppContext } from './contexts';

import { appStart } from './modules/app';
import { getQuery, setQuery, resetQuery } from './modules/query';
Expand All @@ -23,6 +24,8 @@ type Props = {
readKey: string;
/** Keen master access key */
masterKey: string;
/** Modal container selector */
modalContainer: string;
/** Update query event handler */
onUpdateQuery?: (query: Object) => void;
};
Expand Down Expand Up @@ -100,7 +103,11 @@ class QueryCreator extends React.Component<Props> {
render() {
return (
<Provider store={this.store}>
<App />
<AppContext.Provider
value={{ modalContainer: this.props.modalContainer }}
>
<App />
</AppContext.Provider>
</Provider>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ export const Groups = styled.div`
padding: 10px 0;
${List} + ${List} {
margin-top: 10px;
margin-top: 14px;
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { colors } from '@keen.io/colors';
export const Container = styled.li<{
isActive: boolean;
}>`
padding: 7px 10px;
padding: 8px 10px;
display: flex;
align-items: center;
justify-content: space-between;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export const Container = styled.div`
`;

export const Collections = styled.div`
max-height: 140px;
max-height: 240px;
overflow-y: scroll;
`;
33 changes: 33 additions & 0 deletions lib/js/app/queryCreator/components/Portal/Portal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import ReactDOM from 'react-dom';

type Props = {
/** Modal container selector */
modalContainer: string;
/** Modal container selector */
children: React.ReactNode;
};

export default class Portal extends React.Component<Props> {
element: HTMLDivElement;

modalRoot: HTMLDivElement;

constructor(props: Props) {
super(props);
this.element = document.createElement('div');
this.modalRoot = document.querySelector(this.props.modalContainer);
}

componentDidMount() {
this.modalRoot.appendChild(this.element);
}

componentWillUnmount() {
this.modalRoot.removeChild(this.element);
}

render() {
return ReactDOM.createPortal(this.props.children, this.element);
}
}
3 changes: 3 additions & 0 deletions lib/js/app/queryCreator/components/Portal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Portal from './Portal';

export default Portal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react';
import { render as rtlRender, fireEvent } from '@testing-library/react';

import PropertiesTree from './PropertiesTree';

const render = (overProps: any = {}) => {
const props = {
onClick: jest.fn(),
expanded: false,
...overProps,
};

const wrapper = rtlRender(
<PropertiesTree {...props} />
);

return {
props,
wrapper,
};
};

test('allows user to select nested property', () => {
const properties = {
category: ['category', 'string'],
user: {
id: ['user.id', 'number'],
}
};

const { wrapper: { getByText }, props } = render({ properties });

const title = getByText('user');
fireEvent.click(title);

const property = getByText('id');
fireEvent.click(property);

expect(props.onClick.mock.calls[0][1]).toEqual('user.id');
});

test('renders properties from all tree levels', () => {
const properties = {
category: ['category', 'string'],
user: {
details: {
name: ['user.details.name', 'string'],
}
}
};

const { wrapper: { getByText } } = render({ properties, expanded: true });
const property = getByText('name');

expect(property).toBeInTheDocument();
});

test('expands all properties tree levels', () => {
const properties = {
category: ['category', 'string'],
user: {
details: {
name: ['user.details.name', 'string'],
}
}
};

const { wrapper: { getByText, rerender }, props } = render({ properties });
rerender(<PropertiesTree {...props} expanded={true} />)

const property = getByText('name');

expect(property).toBeInTheDocument();
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import React, { FC } from 'react';

import { TreeLevel, TreeLeaf } from './components';
import { TreeLevel } from './components';

import PropertyTreeItem from '../PropertyTreeItem';
import { getPropertyType, getPropertyPath } from './utils';

import { PADDING } from './constants';

type Props = {
/** Properties tree */
Expand All @@ -9,8 +14,6 @@ type Props = {
onClick: (e: React.MouseEvent<HTMLDivElement>, propertyPath: string) => void;
/** Expand all tree levels */
expanded?: boolean;
/** Open indicator */
isOpen?: boolean;
};

const PropertiesTree: FC<Props> = ({ expanded, onClick, properties }) => {
Expand All @@ -21,12 +24,13 @@ const PropertiesTree: FC<Props> = ({ expanded, onClick, properties }) => {
{keys.map((key) => {
if (Array.isArray(properties[key])) {
return (
<TreeLeaf
padding={15}
name={key}
type={properties[key][1]}
<PropertyTreeItem
key={key}
onClick={(e) => onClick(e, properties[key][0])}
padding={PADDING}
propertyName={key}
propertyPath={getPropertyPath(properties[key] as string[])}
type={getPropertyType(properties[key] as string[])}
onClick={(e, propertyPath) => onClick(e, propertyPath)}
/>
);
} else {
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,15 @@ export const Header = styled.div`
cursor: pointer;
`;

export const Title = styled.div`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;

export const MotionIcon = styled(motion.div)`
display: flex;
flex-shrink: 0;
align-items: center;
margin-left: 4px;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import React, { FC, useState, useEffect } from 'react';
import { Icon } from '@keen.io/icons';
import { colors } from '@keen.io/colors';

import { Header, MotionIcon } from './TreeLevel.styles';
import TreeLeaf from '../TreeLeaf';
import { Header, Title, MotionIcon } from './TreeLevel.styles';
import PropertyTreeItem from '../../../PropertyTreeItem';

import { getPropertyType, getPropertyPath } from '../../utils';

import { PADDING } from '../../constants';

Expand Down Expand Up @@ -39,9 +41,9 @@ const TreeLevel: FC<Props> = ({
<div data-testid="tree-level">
<Header
onClick={() => setOpen(!isOpen)}
style={{ paddingLeft: level * PADDING }}
style={{ paddingLeft: level * PADDING, paddingRight: PADDING }}
>
{header}
<Title>{header}</Title>
<MotionIcon
initial={false}
animate={isOpen ? { rotate: 90 } : { rotate: 0 }}
Expand All @@ -58,27 +60,25 @@ const TreeLevel: FC<Props> = ({
keys.map((key) => {
if (Array.isArray(properties[key])) {
return (
<TreeLeaf
<PropertyTreeItem
key={key}
propertyName={key}
propertyPath={getPropertyPath(properties[key] as string[])}
padding={(level + 1) * PADDING}
name={key}
type={properties[key][1]}
onClick={(e) => {
onClick(e, properties[key][0]);
}}
type={getPropertyType(properties[key] as string[])}
onClick={(e, propertyPath) => onClick(e, propertyPath)}
/>
);
} else {
return (
<div key={key}>
<TreeLevel
expanded={expanded}
level={level + 1}
onClick={onClick}
header={key}
properties={properties[key] as Record<string, any>}
/>
</div>
<TreeLevel
key={key}
header={key}
expanded={expanded}
level={level + 1}
onClick={onClick}
properties={properties[key] as Record<string, any>}
/>
);
}
})}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import TreeLevel from './TreeLevel';
import TreeLeaf from './TreeLeaf';

export { TreeLevel, TreeLeaf };
export { TreeLevel };
Loading

0 comments on commit c64d00d

Please sign in to comment.