diff --git a/lib/js/app/components/DataViz/DataViz.styles.ts b/lib/js/app/components/DataViz/DataViz.styles.ts index 3509270f5..d9202f745 100644 --- a/lib/js/app/components/DataViz/DataViz.styles.ts +++ b/lib/js/app/components/DataViz/DataViz.styles.ts @@ -2,7 +2,7 @@ import styled from 'styled-components'; export const VisulizationContainer = styled.div` width: 100%; - height: 340px; + height: 280px; `; export const Settings = styled.div` diff --git a/lib/js/app/components/JSONView/JSONView.styles.ts b/lib/js/app/components/JSONView/JSONView.styles.ts index c5635e277..93f61f20e 100644 --- a/lib/js/app/components/JSONView/JSONView.styles.ts +++ b/lib/js/app/components/JSONView/JSONView.styles.ts @@ -2,6 +2,6 @@ import styled from 'styled-components'; export const Container = styled.div` width: 100%; - height: 340px; + height: 280px; overflow-y: scroll; `; diff --git a/lib/js/app/components/QuerySummary/QuerySummary.styles.ts b/lib/js/app/components/QuerySummary/QuerySummary.styles.ts index c0b2568e3..d962620df 100644 --- a/lib/js/app/components/QuerySummary/QuerySummary.styles.ts +++ b/lib/js/app/components/QuerySummary/QuerySummary.styles.ts @@ -1,41 +1,5 @@ import styled from 'styled-components'; -import { colors } from '@keen.io/colors'; -// import { Card as BaseCard } from '@keen.io/ui-core'; - -// export const Card = styled(BaseCard)` -// height: auto; -// `; export const Wrapper = styled.div` padding: 20px; `; - -export const StyledTable = styled.table` - border-collapse: collapse; - table-layout: fixed; - text-align: left; -`; - -export const StyledBody = styled.tbody``; - -export const Row = styled.tr` - vertical-align: top; -`; - -export const Label = styled.th` - padding: 0 20px 10px 0; - - font-family: 'Lato Bold', sans-serif; - font-size: 14px; - line-height: 17px; - color: ${colors.black[100]}; -`; - -export const Value = styled.td` - padding: 0 0 10px 0; - - font-family: 'Lato Regular', sans-serif; - font-size: 14px; - line-height: 17px; - color: ${colors.black[100]}; -`; diff --git a/lib/js/app/components/QuerySummary/QuerySummary.test.tsx b/lib/js/app/components/QuerySummary/QuerySummary.test.tsx new file mode 100644 index 000000000..5a2e9b4dd --- /dev/null +++ b/lib/js/app/components/QuerySummary/QuerySummary.test.tsx @@ -0,0 +1,75 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import React from 'react'; +import { render as rtlRender } from '@testing-library/react'; + +import QuerySummary from './QuerySummary'; + +const render = (overProps: any = {}) => { + const props = { + querySettings: { + query: { + analysis_type: 'sum', + event_collection: 'purchases', + target_property: 'item.price', + timezone: 'UTC', + filters: [ + { + operator: 'gt', + property_value: '100', + property_name: 'item.quantity', + }, + ], + }, + }, + ...overProps, + }; + + const wrapper = rtlRender(); + + return { + wrapper, + props, + }; +}; + +test('should render query summary', () => { + const { + wrapper: { container }, + } = render(); + + expect(container).toMatchSnapshot(); +}); + +test('should render query summary for funnels', () => { + const querySettings = { + query: { + analysis_type: 'funnel', + steps: [ + { + with_actors: false, + actor_property: 'user.id', + filters: [], + timeframe: 'this_14_days', + timezone: 'UTC', + event_collection: 'signups', + optional: false, + inverted: false, + }, + { + with_actors: false, + actor_property: 'user.id', + filters: [], + timeframe: 'this_14_days', + timezone: 'UTC', + event_collection: 'purchases', + optional: false, + inverted: false, + }, + ], + }, + }; + const { + wrapper: { container }, + } = render({ querySettings }); + expect(container).toMatchSnapshot(); +}); diff --git a/lib/js/app/components/QuerySummary/QuerySummary.tsx b/lib/js/app/components/QuerySummary/QuerySummary.tsx index b3dce37a3..1e9e4d43f 100644 --- a/lib/js/app/components/QuerySummary/QuerySummary.tsx +++ b/lib/js/app/components/QuerySummary/QuerySummary.tsx @@ -1,15 +1,14 @@ import React, { FC } from 'react'; -import { FilterSummary } from './components'; + import { - Wrapper, + Timeframe, + FunnelSteps, + PropertyName, StyledTable, - StyledBody, - Row, - Label, - Value, -} from './QuerySummary.styles'; +} from './components'; +import { Wrapper } from './QuerySummary.styles'; -import { Filter } from './types'; +import text from './text.json'; type Props = { querySettings: Record; @@ -22,50 +21,53 @@ const QuerySummary: FC = ({ querySettings }) => { event_collection: eventCollection, target_property: targetProperty, timeframe, + timezone, filters, + steps, }, } = querySettings; return ( - - + + {analysisType && ( - - - {analysisType} - + + {text.analysis} + {analysisType} + )} {eventCollection && ( - - - {eventCollection} - + + {text.eventStream} + {eventCollection} + )} {targetProperty && ( - - - {targetProperty} - + + {text.targetProperty} + + + + )} {timeframe && ( - - - {timeframe} - + + {text.timeframe} + + + + )} {!!filters?.length && ( - - - - {filters.map((filter: Filter, idx: number) => ( - - ))} - - + + {text.appliedFilters} + {filters.length} + )} - - + + + {steps && } ); }; diff --git a/lib/js/app/components/QuerySummary/__snapshots__/QuerySummary.test.tsx.snap b/lib/js/app/components/QuerySummary/__snapshots__/QuerySummary.test.tsx.snap new file mode 100644 index 000000000..ac9608f40 --- /dev/null +++ b/lib/js/app/components/QuerySummary/__snapshots__/QuerySummary.test.tsx.snap @@ -0,0 +1,203 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render query summary 1`] = ` +
+
+ + + + + + + + + + + + + + + + + + + +
+ Analysis: + + sum +
+ Event Stream: + + purchases +
+ Target Property: + +
+ + item + + + + + + +
+
+ + price + +
+
+ Applied Filters: + + 1 +
+
+
+`; + +exports[`should render query summary for funnels 1`] = ` +
+
+ + + + + + + +
+ Analysis: + + funnel +
+
+
+
+ + + +
+
+ Step + + 1 +
+
+ signups +
+
+
+
+
+
+ + + +
+
+ Step + + 2 +
+
+ purchases +
+
+
+
+
+`; diff --git a/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/AbsoluteTimeframe.styles.ts b/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/AbsoluteTimeframe.styles.ts new file mode 100644 index 000000000..578579a95 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/AbsoluteTimeframe.styles.ts @@ -0,0 +1,12 @@ +import styled from 'styled-components'; +import { transparentize } from 'polished'; +import { colors } from '@keen.io/colors'; + +export const Container = styled.div` + display: inline-flex; +`; + +export const Separator = styled.span` + margin: 0 5px; + color: ${transparentize(0.4, colors.blue[500])}; +`; diff --git a/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/AbsoluteTimeframe.test.tsx b/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/AbsoluteTimeframe.test.tsx new file mode 100644 index 000000000..f76e18af7 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/AbsoluteTimeframe.test.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { render as rtlRender } from '@testing-library/react'; + +import AbsoluteTimeframe from './AbsoluteTimeframe'; + +const render = (overProps: any = {}) => { + const props = { + timezone: 'UTC', + timeframe: { + start: '2020-09-29T00:00:00Z', + end: '2020-09-30T00:00:00Z', + }, + ...overProps, + }; + + const wrapper = rtlRender(); + + return { + wrapper, + props, + }; +}; + +test('renders component', () => { + const { + wrapper: { container }, + } = render(); + + expect(container).toMatchSnapshot(); +}); + +test('renders component when timezone as number provided', () => { + const { + wrapper: { container }, + } = render({ timezone: 0 }); + + expect(container).toMatchSnapshot(); +}); diff --git a/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/AbsoluteTimeframe.tsx b/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/AbsoluteTimeframe.tsx new file mode 100644 index 000000000..725979ad9 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/AbsoluteTimeframe.tsx @@ -0,0 +1,32 @@ +import React, { FC } from 'react'; +import moment from 'moment-timezone'; + +import { getTimezoneValue } from '../../../../queryCreator'; +import { Container, Separator } from './AbsoluteTimeframe.styles'; + +import { Timezones } from '../../../../queryCreator'; + +import text from './text.json'; + +type Props = { + timeframe: { + start: string; + end: string; + }; + timezone: Timezones | number; +}; + +const AbsoluteTimeframe: FC = ({ timeframe, timezone }) => { + const { start, end } = timeframe; + const namedTimezone = getTimezoneValue(timezone); + + return ( + + {moment(start).tz(namedTimezone).format('YYYY-MM-DD HH:mm')} + {text.separator} + {moment(end).tz(namedTimezone).format('YYYY-MM-DD HH:mm')} + + ); +}; + +export default AbsoluteTimeframe; diff --git a/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/__snapshots__/AbsoluteTimeframe.test.tsx.snap b/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/__snapshots__/AbsoluteTimeframe.test.tsx.snap new file mode 100644 index 000000000..ba2d729c4 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/__snapshots__/AbsoluteTimeframe.test.tsx.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders component 1`] = ` +
+
+ 2020-09-29 00:00 + + to + + 2020-09-30 00:00 +
+
+`; + +exports[`renders component when timezone as number provided 1`] = ` +
+
+ 2020-09-29 00:00 + + to + + 2020-09-30 00:00 +
+
+`; diff --git a/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/index.ts b/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/index.ts new file mode 100644 index 000000000..68f9b3231 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/index.ts @@ -0,0 +1,3 @@ +import AbsoluteTimeframe from './AbsoluteTimeframe'; + +export default AbsoluteTimeframe; diff --git a/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/text.json b/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/text.json new file mode 100644 index 000000000..76ef42453 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/AbsoluteTimeframe/text.json @@ -0,0 +1,3 @@ +{ + "separator": "to" +} diff --git a/lib/js/app/components/QuerySummary/components/FilterSummary/FilterSummary.styles.ts b/lib/js/app/components/QuerySummary/components/FilterSummary/FilterSummary.styles.ts deleted file mode 100644 index 34bf5ebd3..000000000 --- a/lib/js/app/components/QuerySummary/components/FilterSummary/FilterSummary.styles.ts +++ /dev/null @@ -1,27 +0,0 @@ -import styled from 'styled-components'; -import { colors } from '@keen.io/colors'; -import { transparentize } from 'polished'; - -export const Wrapper = styled.div` - display: flex; - flex-wrap: wrap; - - font-family: 'Lato Regular', sans-serif; - font-size: 14px; - line-height: 17px; - - color: ${colors.black[100]}; - - & + & { - margin-top: 5px; - } -`; - -export const Operator = styled.div` - padding-left: 5px; - color: ${transparentize(0.5, colors.black[100])}; -`; - -export const Value = styled.div` - padding-left: 5px; -`; diff --git a/lib/js/app/components/QuerySummary/components/FilterSummary/FilterSummary.tsx b/lib/js/app/components/QuerySummary/components/FilterSummary/FilterSummary.tsx deleted file mode 100644 index 23ba3f4b1..000000000 --- a/lib/js/app/components/QuerySummary/components/FilterSummary/FilterSummary.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React, { FC } from 'react'; -import PropertyName from '../PropertyName'; -// import { TYPES_CONFIG } from '../../../../queryCreator/components/Filters/constants'; - -import { Wrapper, Operator, Value } from './FilterSummary.styles'; - -import { Filter } from '../../types'; - -// type Filter = { -// operator: string; -// property_name: string; -// property_value: string; -// property_type?: string; -// } - -type Props = { - filter: Filter; -}; - -const FilterSummary: FC = ({ filter }) => { - const { - operator, - property_name: propertyName, - property_value: propertyValue, - } = filter; - - // const operatorLabel = property_type ? TYPES_CONFIG[property_type][operator] : operator; - - return ( - - - {operator} - {propertyValue} - - ); -}; - -export default FilterSummary; diff --git a/lib/js/app/components/QuerySummary/components/FilterSummary/index.ts b/lib/js/app/components/QuerySummary/components/FilterSummary/index.ts deleted file mode 100644 index 4d8528b3c..000000000 --- a/lib/js/app/components/QuerySummary/components/FilterSummary/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import FilterSummary from './FilterSummary'; - -export default FilterSummary; diff --git a/lib/js/app/components/QuerySummary/components/FunnelStep/FunnelStep.styles.ts b/lib/js/app/components/QuerySummary/components/FunnelStep/FunnelStep.styles.ts new file mode 100644 index 000000000..195b10280 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/FunnelStep/FunnelStep.styles.ts @@ -0,0 +1,52 @@ +import styled, { css } from 'styled-components'; +import { colors } from '@keen.io/colors'; + +export const Container = styled.div` + border: 1px solid ${colors.gray[300]}; + + & + & { + border-top: none; + } +`; + +export const Header = styled.div<{ isOpen: boolean }>` + padding: 10px 12px; + + display: flex; + align-items: center; + + cursor: pointer; + + ${(props) => + props.isOpen && + css` + background-color: ${colors.gray[100]}; + border-bottom: 1px solid ${colors.gray[300]}; + `} +`; + +export const IconContainer = styled.div` + margin-right: 10px; +`; + +export const StepNumber = styled.div` + margin-right: 10px; + + font-family: 'Lato Bold', sans-serif; + font-size: 14px; + line-height: 17px; + + color: ${colors.black[100]}; +`; + +export const Title = styled.div` + font-family: 'Lato Regular', sans-serif; + font-size: 14px; + line-height: 17px; + + color: ${colors.black[100]}; +`; + +export const Content = styled.div` + padding: 10px 10px 0 10px; +`; diff --git a/lib/js/app/components/QuerySummary/components/FunnelStep/FunnelStep.test.tsx b/lib/js/app/components/QuerySummary/components/FunnelStep/FunnelStep.test.tsx new file mode 100644 index 000000000..1237353a6 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/FunnelStep/FunnelStep.test.tsx @@ -0,0 +1,54 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import React from 'react'; +import { render as rtlRender, fireEvent } from '@testing-library/react'; + +import FunnelStep from './FunnelStep'; + +const render = (overProps: any = {}) => { + const props = { + index: 0, + step: { + with_actors: false, + actor_property: 'item.price', + filters: [], + timeframe: 'this_14_days', + timezone: -25200, + event_collection: 'purchases', + optional: false, + inverted: false, + }, + ...overProps, + }; + + const wrapper = rtlRender(); + + return { + wrapper, + props, + }; +}; + +test('should render only header with title', () => { + const { + wrapper: { getByText, queryByText }, + props, + } = render(); + + const header = getByText(props.step.event_collection); + const timeframe = queryByText(props.step.timeframe); + expect(header).toBeInTheDocument(); + expect(timeframe).toBeNull(); +}); + +test('should render step details by clicking on header', () => { + const { + wrapper: { getByText, queryByText }, + props, + } = render(); + + const header = getByText(props.step.event_collection); + fireEvent.click(header); + + const timeframe = queryByText(props.step.timeframe); + expect(timeframe).toBeInTheDocument(); +}); diff --git a/lib/js/app/components/QuerySummary/components/FunnelStep/FunnelStep.tsx b/lib/js/app/components/QuerySummary/components/FunnelStep/FunnelStep.tsx new file mode 100644 index 000000000..637bbac07 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/FunnelStep/FunnelStep.tsx @@ -0,0 +1,91 @@ +import React, { FC, useState } from 'react'; +import { transparentize } from 'polished'; +import { Icon } from '@keen.io/icons'; +import { colors } from '@keen.io/colors'; + +import Timeframe from '../Timeframe'; +import StyledTable from '../Table'; +import PropertyName from '../PropertyName'; + +import { + Container, + Header, + IconContainer, + StepNumber, + Title, + Content, +} from './FunnelStep.styles'; + +import { FunnelStep } from '../../types'; +import text from './text.json'; + +type Props = { + step: FunnelStep; + index: number; +}; + +const FunnelStep: FC = ({ step, index }) => { + const [open, setOpen] = useState(false); + + const { + event_collection: eventCollection, + actor_property: actorProperty, + timeframe, + timezone, + filters, + } = step; + return ( + +
setOpen(!open)} isOpen={open}> + + + + + {text.step} {index + 1} + + {eventCollection} +
+ {open && ( + + + + {eventCollection && ( + + {text.eventStream} + {eventCollection} + + )} + {actorProperty && ( + + {text.targetProperty} + + + + + )} + {timeframe && ( + + {text.timeframe} + + + + + )} + {!!filters?.length && ( + + {text.appliedFilters} + {filters.length} + + )} + + + + )} +
+ ); +}; + +export default FunnelStep; diff --git a/lib/js/app/components/QuerySummary/components/FunnelStep/index.ts b/lib/js/app/components/QuerySummary/components/FunnelStep/index.ts new file mode 100644 index 000000000..7e1b6dd2f --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/FunnelStep/index.ts @@ -0,0 +1,3 @@ +import FunnelStep from './FunnelStep'; + +export default FunnelStep; diff --git a/lib/js/app/components/QuerySummary/components/FunnelStep/text.json b/lib/js/app/components/QuerySummary/components/FunnelStep/text.json new file mode 100644 index 000000000..5ea76c297 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/FunnelStep/text.json @@ -0,0 +1,7 @@ +{ + "step": "Step", + "eventStream": "Event Stream:", + "targetProperty": "Target Property:", + "timeframe": "Timeframe:", + "appliedFilters": "Applied Filters:" +} diff --git a/lib/js/app/components/QuerySummary/components/FunnelSteps/FunnelSteps.test.tsx b/lib/js/app/components/QuerySummary/components/FunnelSteps/FunnelSteps.test.tsx new file mode 100644 index 000000000..de8be3e21 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/FunnelSteps/FunnelSteps.test.tsx @@ -0,0 +1,50 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import React from 'react'; +import { render as rtlRender } from '@testing-library/react'; + +import FunnelSteps from './FunnelSteps'; + +const render = (overProps: any = {}) => { + const props = { + steps: [ + { + with_actors: false, + actor_property: 'item.price', + filters: [], + timeframe: 'this_14_days', + timezone: -25200, + event_collection: 'purchases', + optional: false, + inverted: false, + }, + { + with_actors: false, + actor_property: 'item.amount', + filters: [], + timeframe: 'this_14_days', + timezone: -25200, + event_collection: 'purchases', + optional: false, + inverted: false, + }, + ], + ...overProps, + }; + + const wrapper = rtlRender(); + + return { + wrapper, + props, + }; +}; + +test('should render correct number of steps', () => { + const { + wrapper: { queryAllByText }, + props, + } = render(); + + const steps = queryAllByText('purchases'); + expect(steps.length).toEqual(props.steps.length); +}); diff --git a/lib/js/app/components/QuerySummary/components/FunnelSteps/FunnelSteps.tsx b/lib/js/app/components/QuerySummary/components/FunnelSteps/FunnelSteps.tsx new file mode 100644 index 000000000..12490c5f9 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/FunnelSteps/FunnelSteps.tsx @@ -0,0 +1,20 @@ +import React, { FC } from 'react'; +import { FunnelStep as FunnelStepType } from '../../types'; + +import FunnelStep from '../FunnelStep'; + +type Props = { + steps: FunnelStepType[]; +}; + +const FunnelSteps: FC = ({ steps }) => { + return ( + <> + {steps.map((step, idx) => ( + + ))} + + ); +}; + +export default FunnelSteps; diff --git a/lib/js/app/components/QuerySummary/components/FunnelSteps/index.ts b/lib/js/app/components/QuerySummary/components/FunnelSteps/index.ts new file mode 100644 index 000000000..264ccdc38 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/FunnelSteps/index.ts @@ -0,0 +1,3 @@ +import FunnelSteps from './FunnelSteps'; + +export default FunnelSteps; diff --git a/lib/js/app/components/QuerySummary/components/PropertyName/PropertyName.styles.ts b/lib/js/app/components/QuerySummary/components/PropertyName/PropertyName.styles.ts index 51bd12e4f..321f27e1f 100644 --- a/lib/js/app/components/QuerySummary/components/PropertyName/PropertyName.styles.ts +++ b/lib/js/app/components/QuerySummary/components/PropertyName/PropertyName.styles.ts @@ -1,9 +1,12 @@ import styled from 'styled-components'; -import { colors } from '@keen.io/colors'; -export const Divider = styled.span` - margin-left: 5px; - margin-right: 5px; +export const Container = styled.div` + display: inline-flex; + align-items: center; +`; - color: ${colors.black[100]}; +export const IconContainer = styled.span` + display: inline-block; + margin-left: 3px; + margin-right: 3px; `; diff --git a/lib/js/app/components/QuerySummary/components/PropertyName/PropertyName.test.tsx b/lib/js/app/components/QuerySummary/components/PropertyName/PropertyName.test.tsx new file mode 100644 index 000000000..ef6a33257 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/PropertyName/PropertyName.test.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { render as rtlRender } from '@testing-library/react'; + +import PropertyName from './PropertyName'; + +const render = (overProps: any = {}) => { + const props = { + name: 'item.price', + ...overProps, + }; + + const wrapper = rtlRender(); + + return { + wrapper, + props, + }; +}; + +test('should render ', () => { + const { + wrapper: { container }, + } = render(); + + expect(container).toMatchSnapshot(); +}); diff --git a/lib/js/app/components/QuerySummary/components/PropertyName/PropertyName.tsx b/lib/js/app/components/QuerySummary/components/PropertyName/PropertyName.tsx index ac29b58cc..27f557694 100644 --- a/lib/js/app/components/QuerySummary/components/PropertyName/PropertyName.tsx +++ b/lib/js/app/components/QuerySummary/components/PropertyName/PropertyName.tsx @@ -1,23 +1,32 @@ import React, { FC } from 'react'; +import { Icon } from '@keen.io/icons'; +import { colors } from '@keen.io/colors'; -import { Divider } from './PropertyName.styles'; +import { Container, IconContainer } from './PropertyName.styles'; type Props = { name: string; }; -const DIVIDER_MARK = '>'; - const PropertyName: FC = ({ name }) => { const nameArr = name.split('.'); return ( <> {nameArr.map((item, idx) => ( - - {item} - {idx < nameArr.length - 1 && {DIVIDER_MARK}} - + + {item} + {idx < nameArr.length - 1 && ( + + + + )} + ))} ); diff --git a/lib/js/app/components/QuerySummary/components/PropertyName/__snapshots__/PropertyName.test.tsx.snap b/lib/js/app/components/QuerySummary/components/PropertyName/__snapshots__/PropertyName.test.tsx.snap new file mode 100644 index 000000000..9ad1a99e3 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/PropertyName/__snapshots__/PropertyName.test.tsx.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` +
+
+ + item + + + + + + +
+
+ + price + +
+
+`; diff --git a/lib/js/app/components/QuerySummary/components/Table/Table.styles.ts b/lib/js/app/components/QuerySummary/components/Table/Table.styles.ts new file mode 100644 index 000000000..509bf2d44 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/Table/Table.styles.ts @@ -0,0 +1,40 @@ +import styled from 'styled-components'; +import { colors } from '@keen.io/colors'; + +export const StyledTable = styled.table` + margin: 0 0 10px 0; + padding: 0; + border-collapse: collapse; + table-layout: fixed; + text-align: left; +`; + +export const StyledBody = styled.tbody``; + +export const Label = styled.th` + padding: 0 20px 10px 0; + + font-family: 'Lato Bold', sans-serif; + font-size: 14px; + line-height: 17px; + color: ${colors.black[100]}; +`; + +export const Value = styled.td` + padding: 0 0 10px 0; + + font-family: 'Lato Regular', sans-serif; + font-size: 14px; + line-height: 17px; + color: ${colors.black[100]}; +`; + +export const Row = styled.tr` + vertical-align: top; + + &:last-child { + ${Label}, ${Value} { + padding-bottom: 0; + } + } +`; diff --git a/lib/js/app/components/QuerySummary/components/Table/Table.test.tsx b/lib/js/app/components/QuerySummary/components/Table/Table.test.tsx new file mode 100644 index 000000000..c8db7f76a --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/Table/Table.test.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { render } from '@testing-library/react'; + +import StyledTable from './index'; + +test('should render ', () => { + const { container } = render( + + + + Label + Value + + + + ); + + expect(container).toMatchSnapshot(); +}); diff --git a/lib/js/app/components/QuerySummary/components/Table/Table.tsx b/lib/js/app/components/QuerySummary/components/Table/Table.tsx new file mode 100644 index 000000000..06652bc82 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/Table/Table.tsx @@ -0,0 +1,14 @@ +import React, { FC } from 'react'; + +import { StyledTable } from './Table.styles'; + +type Props = { + /** Children nodes */ + children: React.ReactNode; +}; + +export const Table: FC = ({ children }) => ( + {children} +); + +export default Table; diff --git a/lib/js/app/components/QuerySummary/components/Table/__snapshots__/Table.test.tsx.snap b/lib/js/app/components/QuerySummary/components/Table/__snapshots__/Table.test.tsx.snap new file mode 100644 index 000000000..4d7292cf1 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/Table/__snapshots__/Table.test.tsx.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` +
+ + + + + + + +
+ Label + + Value +
+
+`; diff --git a/lib/js/app/components/QuerySummary/components/Table/index.ts b/lib/js/app/components/QuerySummary/components/Table/index.ts new file mode 100644 index 000000000..b7cf31729 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/Table/index.ts @@ -0,0 +1,12 @@ +import Table from './Table'; +import { StyledBody as Body, Row, Label, Value } from './Table.styles'; + +const StyledTable = { + Table, + Body, + Row, + Label, + Value, +}; + +export default StyledTable; diff --git a/lib/js/app/components/QuerySummary/components/Timeframe/Timeframe.styles.ts b/lib/js/app/components/QuerySummary/components/Timeframe/Timeframe.styles.ts new file mode 100644 index 000000000..63f9eca43 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/Timeframe/Timeframe.styles.ts @@ -0,0 +1,12 @@ +import styled from 'styled-components'; +import { transparentize } from 'polished'; +import { colors } from '@keen.io/colors'; + +export const Container = styled.div` + display: flex; +`; + +export const Separator = styled.span` + margin: 0 5px; + color: ${transparentize(0.4, colors.blue[500])}; +`; diff --git a/lib/js/app/components/QuerySummary/components/Timeframe/Timeframe.test.tsx b/lib/js/app/components/QuerySummary/components/Timeframe/Timeframe.test.tsx new file mode 100644 index 000000000..0a9202499 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/Timeframe/Timeframe.test.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { render as rtlRender } from '@testing-library/react'; + +import Timeframe from './Timeframe'; + +const render = (overProps: any = {}) => { + const props = { + timeframe: 'this_14_days', + timezone: 'UTC', + ...overProps, + }; + + const wrapper = rtlRender(); + + return { + wrapper, + props, + }; +}; + +test('should render relative timeframe', () => { + const { + wrapper: { getByText }, + props, + } = render(); + + const timeframe = getByText(props.timeframe); + expect(timeframe).toBeInTheDocument(); +}); + +test('should render absolute timeframe', () => { + const { + wrapper: { container }, + } = render({ + timeframe: { start: '2020-09-29T00:00:00Z', end: '2020-09-30T00:00:00Z' }, + }); + + expect(container).toMatchSnapshot(); +}); diff --git a/lib/js/app/components/QuerySummary/components/Timeframe/Timeframe.tsx b/lib/js/app/components/QuerySummary/components/Timeframe/Timeframe.tsx new file mode 100644 index 000000000..12f7e6f03 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/Timeframe/Timeframe.tsx @@ -0,0 +1,18 @@ +import React, { FC } from 'react'; + +import AbsoluteTimeframe from '../AbsoluteTimeframe'; +import { Timeframe, Timezones } from '../../../../queryCreator'; + +type Props = { + timeframe: Timeframe; + timezone?: Timezones | number; +}; + +const Timeframe: FC = ({ timeframe, timezone }) => + typeof timeframe !== 'string' ? ( + + ) : ( + {timeframe} + ); + +export default Timeframe; diff --git a/lib/js/app/components/QuerySummary/components/Timeframe/__snapshots__/Timeframe.test.tsx.snap b/lib/js/app/components/QuerySummary/components/Timeframe/__snapshots__/Timeframe.test.tsx.snap new file mode 100644 index 000000000..38faec801 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/Timeframe/__snapshots__/Timeframe.test.tsx.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render absolute timeframe 1`] = ` +
+
+ 2020-09-29 00:00 + + to + + 2020-09-30 00:00 +
+
+`; diff --git a/lib/js/app/components/QuerySummary/components/Timeframe/index.ts b/lib/js/app/components/QuerySummary/components/Timeframe/index.ts new file mode 100644 index 000000000..61f683a91 --- /dev/null +++ b/lib/js/app/components/QuerySummary/components/Timeframe/index.ts @@ -0,0 +1,3 @@ +import Timeframe from './Timeframe'; + +export default Timeframe; diff --git a/lib/js/app/components/QuerySummary/components/index.ts b/lib/js/app/components/QuerySummary/components/index.ts index ef936c45b..a4f053036 100644 --- a/lib/js/app/components/QuerySummary/components/index.ts +++ b/lib/js/app/components/QuerySummary/components/index.ts @@ -1,3 +1,6 @@ -import FilterSummary from './FilterSummary'; +import Timeframe from './Timeframe'; +import FunnelSteps from './FunnelSteps'; +import PropertyName from './PropertyName'; +import StyledTable from './Table'; -export { FilterSummary }; +export { Timeframe, FunnelSteps, PropertyName, StyledTable }; diff --git a/lib/js/app/components/QuerySummary/text.json b/lib/js/app/components/QuerySummary/text.json new file mode 100644 index 000000000..96a597811 --- /dev/null +++ b/lib/js/app/components/QuerySummary/text.json @@ -0,0 +1,7 @@ +{ + "analysis": "Analysis:", + "eventStream": "Event Stream:", + "targetProperty": "Target Property:", + "timeframe": "Timeframe:", + "appliedFilters": "Applied Filters:" +} diff --git a/lib/js/app/components/QuerySummary/types.ts b/lib/js/app/components/QuerySummary/types.ts index 6fc3c7002..1d551eed0 100644 --- a/lib/js/app/components/QuerySummary/types.ts +++ b/lib/js/app/components/QuerySummary/types.ts @@ -1,6 +1,19 @@ +import { Timeframe, Timezones } from '../../queryCreator'; + export type Filter = { operator: string; property_name: string; property_value: string; property_type?: string; }; + +export type FunnelStep = { + actor_property: string; + event_collection: string; + inverted: boolean; + optional: boolean; + timeframe: Timeframe; + timezone?: Timezones | number; + with_actors: boolean; + filters: Filter[]; +}; diff --git a/lib/js/app/components/QueryVisualization/QueryVisualization.styles.ts b/lib/js/app/components/QueryVisualization/QueryVisualization.styles.ts index 7d4813354..81545153c 100644 --- a/lib/js/app/components/QueryVisualization/QueryVisualization.styles.ts +++ b/lib/js/app/components/QueryVisualization/QueryVisualization.styles.ts @@ -3,3 +3,7 @@ import styled from 'styled-components'; export const Settings = styled.div` display: flex; `; + +export const Container = styled.div` + height: 360px; +`; diff --git a/lib/js/app/components/QueryVisualization/QueryVisualization.tsx b/lib/js/app/components/QueryVisualization/QueryVisualization.tsx index 28cfc249f..28197ea81 100644 --- a/lib/js/app/components/QueryVisualization/QueryVisualization.tsx +++ b/lib/js/app/components/QueryVisualization/QueryVisualization.tsx @@ -4,7 +4,7 @@ import { Label, Select, Button } from '@keen.io/ui-core'; import { parseQuery } from '@keen.io/parser'; import { colors } from '@keen.io/colors'; -import { Settings } from './QueryVisualization.styles'; +import { Settings, Container } from './QueryVisualization.styles'; import text from './text.json'; import DataViz from '../DataViz'; @@ -72,7 +72,7 @@ const QueryVisualization: FC = ({ queryResults, query }) => { const showDataviz = widgetType !== 'json'; return ( -
+ {showDataviz ? ( = ({ queryResults, query }) => { />
- + ); }; diff --git a/lib/js/app/components/VisualizationPlaceholder/VisualizationPlaceholder.styles.ts b/lib/js/app/components/VisualizationPlaceholder/VisualizationPlaceholder.styles.ts index 403170b00..d2c7bec57 100644 --- a/lib/js/app/components/VisualizationPlaceholder/VisualizationPlaceholder.styles.ts +++ b/lib/js/app/components/VisualizationPlaceholder/VisualizationPlaceholder.styles.ts @@ -10,7 +10,7 @@ export const Container = styled.div` justify-content: center; width: 100%; - height: 160px; + height: 360px; `; type TextProps = { diff --git a/lib/js/app/queryCreator/index.ts b/lib/js/app/queryCreator/index.ts index 6cf5d72a7..5a088815f 100644 --- a/lib/js/app/queryCreator/index.ts +++ b/lib/js/app/queryCreator/index.ts @@ -1,5 +1,15 @@ import QueryCreator from './QueryCreator'; import { SET_QUERY_EVENT, NEW_QUERY_EVENT } from './constants'; +import { TIMEZONES } from './components/Timezone/constants'; +import { getTimezoneValue } from './components/Timezone/utils/getTimezoneValue'; +import { Timeframe, Timezones } from './types'; export default QueryCreator; -export { SET_QUERY_EVENT, NEW_QUERY_EVENT }; +export { + SET_QUERY_EVENT, + NEW_QUERY_EVENT, + TIMEZONES, + Timeframe, + Timezones, + getTimezoneValue, +};