From 827102d90794396a6b842c6a47c402f150d2fa84 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Fri, 7 Jul 2023 20:38:43 -0700 Subject: [PATCH] Scaffolding for insights report. --- .../reports/event-data/EventDataParameters.js | 12 +- .../reports/event-data/EventDataReport.js | 4 +- .../pages/reports/insights/FieldAddForm.js | 44 +++++ .../reports/insights/FieldAddForm.module.css | 38 +++++ .../reports/insights/InsightsParameters.js | 151 ++++++++++++++++++ .../insights/InsightsParameters.module.css | 12 ++ .../pages/reports/insights/InsightsReport.js | 26 +++ .../pages/reports/insights/InsightsTable.js | 19 +++ queries/analytics/eventData/getEventData.ts | 16 +- 9 files changed, 305 insertions(+), 17 deletions(-) create mode 100644 components/pages/reports/insights/FieldAddForm.js create mode 100644 components/pages/reports/insights/FieldAddForm.module.css create mode 100644 components/pages/reports/insights/InsightsParameters.js create mode 100644 components/pages/reports/insights/InsightsParameters.module.css create mode 100644 components/pages/reports/insights/InsightsReport.js create mode 100644 components/pages/reports/insights/InsightsTable.js diff --git a/components/pages/reports/event-data/EventDataParameters.js b/components/pages/reports/event-data/EventDataParameters.js index 93270813..09358be2 100644 --- a/components/pages/reports/event-data/EventDataParameters.js +++ b/components/pages/reports/event-data/EventDataParameters.js @@ -41,7 +41,6 @@ export function EventDataParameters() { const parameterGroups = [ { label: formatMessage(labels.fields), group: REPORT_PARAMETERS.fields }, { label: formatMessage(labels.filters), group: REPORT_PARAMETERS.filters }, - { label: formatMessage(labels.breakdown), group: REPORT_PARAMETERS.groups }, ]; const parameterData = { @@ -55,11 +54,9 @@ export function EventDataParameters() { }; const handleAdd = (group, value) => { - const data = parameterData[group]; + const data = parameterData[group].filter(({ name }) => name !== value.name); - if (!data.find(({ name }) => name === value.name)) { - updateReport({ parameters: { [group]: data.concat(value) } }); - } + updateReport({ parameters: { [group]: data.concat(value) } }); }; const handleRemove = (group, index) => { @@ -127,11 +124,6 @@ export function EventDataParameters() {
{value[1]}
)} - {group === REPORT_PARAMETERS.groups && ( - <> -
{name}
- - )} ); }} diff --git a/components/pages/reports/event-data/EventDataReport.js b/components/pages/reports/event-data/EventDataReport.js index b3358ecf..eb49a29d 100644 --- a/components/pages/reports/event-data/EventDataReport.js +++ b/components/pages/reports/event-data/EventDataReport.js @@ -3,12 +3,12 @@ import ReportHeader from '../ReportHeader'; import ReportMenu from '../ReportMenu'; import ReportBody from '../ReportBody'; import EventDataParameters from './EventDataParameters'; -import Nodes from 'assets/nodes.svg'; import EventDataTable from './EventDataTable'; +import Nodes from 'assets/nodes.svg'; const defaultParameters = { type: 'event-data', - parameters: { fields: [], filters: [], groups: [] }, + parameters: { fields: [], filters: [] }, }; export default function EventDataReport({ reportId }) { diff --git a/components/pages/reports/insights/FieldAddForm.js b/components/pages/reports/insights/FieldAddForm.js new file mode 100644 index 00000000..c95fcac3 --- /dev/null +++ b/components/pages/reports/insights/FieldAddForm.js @@ -0,0 +1,44 @@ +import { useState } from 'react'; +import { createPortal } from 'react-dom'; +import { REPORT_PARAMETERS } from 'lib/constants'; +import PopupForm from '../PopupForm'; +import FieldSelectForm from '../FieldSelectForm'; +import FieldAggregateForm from '../FieldAggregateForm'; +import FieldFilterForm from '../FieldFilterForm'; +import styles from './FieldAddForm.module.css'; + +export function FieldAddForm({ fields = [], group, element, onAdd, onClose }) { + const [selected, setSelected] = useState(); + + const handleSelect = value => { + const { type } = value; + + if (group === REPORT_PARAMETERS.groups || type === 'array' || type === 'boolean') { + value.value = group === REPORT_PARAMETERS.groups ? '' : 'total'; + handleSave(value); + return; + } + + setSelected(value); + }; + + const handleSave = value => { + onAdd(group, value); + onClose(); + }; + + return createPortal( + + {!selected && } + {selected && group === REPORT_PARAMETERS.fields && ( + + )} + {selected && group === REPORT_PARAMETERS.filters && ( + + )} + , + document.body, + ); +} + +export default FieldAddForm; diff --git a/components/pages/reports/insights/FieldAddForm.module.css b/components/pages/reports/insights/FieldAddForm.module.css new file mode 100644 index 00000000..5c5aaa4f --- /dev/null +++ b/components/pages/reports/insights/FieldAddForm.module.css @@ -0,0 +1,38 @@ +.menu { + width: 360px; + max-height: 300px; + overflow: auto; +} + +.item { + display: flex; + flex-direction: row; + justify-content: space-between; + border-radius: var(--border-radius); +} + +.item:hover { + background: var(--base75); +} + +.type { + color: var(--font-color300); +} + +.selected { + font-weight: bold; +} + +.popup { + display: flex; +} + +.filter { + display: flex; + flex-direction: column; + gap: 20px; +} + +.dropdown { + min-width: 60px; +} diff --git a/components/pages/reports/insights/InsightsParameters.js b/components/pages/reports/insights/InsightsParameters.js new file mode 100644 index 00000000..39bfc2e8 --- /dev/null +++ b/components/pages/reports/insights/InsightsParameters.js @@ -0,0 +1,151 @@ +import { useContext, useRef } from 'react'; +import { useApi, useMessages } from 'hooks'; +import { Form, FormRow, FormButtons, SubmitButton, PopupTrigger, Icon, Popup } from 'react-basics'; +import { ReportContext } from 'components/pages/reports/Report'; +import Empty from 'components/common/Empty'; +import { DATA_TYPES, REPORT_PARAMETERS } from 'lib/constants'; +import Icons from 'components/icons'; +import FieldAddForm from './FieldAddForm'; +import BaseParameters from '../BaseParameters'; +import ParameterList from '../ParameterList'; +import styles from './InsightsParameters.module.css'; + +function useFields(websiteId, startDate, endDate) { + const { get, useQuery } = useApi(); + const { data, error, isLoading } = useQuery( + ['fields', websiteId, startDate, endDate], + () => + get('/reports/event-data', { + websiteId, + startAt: +startDate, + endAt: +endDate, + }), + { enabled: !!(websiteId && startDate && endDate) }, + ); + + return { data, error, isLoading }; +} + +export function InsightsParameters() { + const { report, runReport, updateReport, isRunning } = useContext(ReportContext); + const { formatMessage, labels, messages } = useMessages(); + const ref = useRef(null); + const { parameters } = report || {}; + const { websiteId, dateRange, fields, filters, groups } = parameters || {}; + const { startDate, endDate } = dateRange || {}; + const queryEnabled = websiteId && dateRange && fields?.length; + const { data, error } = useFields(websiteId, startDate, endDate); + const parametersSelected = websiteId && startDate && endDate; + const hasData = data?.length !== 0; + + const parameterGroups = [ + { label: formatMessage(labels.fields), group: REPORT_PARAMETERS.fields }, + { label: formatMessage(labels.filters), group: REPORT_PARAMETERS.filters }, + { label: formatMessage(labels.breakdown), group: REPORT_PARAMETERS.groups }, + ]; + + const parameterData = { + fields, + filters, + groups, + }; + + const handleSubmit = values => { + runReport(values); + }; + + const handleAdd = (group, value) => { + const data = parameterData[group]; + + if (!data.find(({ name }) => name === value.name)) { + updateReport({ parameters: { [group]: data.concat(value) } }); + } + }; + + const handleRemove = (group, index) => { + const data = [...parameterData[group]]; + data.splice(index, 1); + updateReport({ parameters: { [group]: data } }); + }; + + const AddButton = ({ group }) => { + return ( + + + + + + {(close, element) => { + return ( + ({ + name: eventKey, + type: DATA_TYPES[InsightsType], + }))} + group={group} + element={element} + onAdd={handleAdd} + onClose={close} + /> + ); + }} + + + ); + }; + + return ( +
+ + {!hasData && } + {parametersSelected && + hasData && + parameterGroups.map(({ label, group }) => { + return ( + } + > + handleRemove(group, index)} + > + {({ name, value }) => { + return ( +
+ {group === REPORT_PARAMETERS.fields && ( + <> +
{name}
+
{value}
+ + )} + {group === REPORT_PARAMETERS.filters && ( + <> +
{name}
+
{value[0]}
+
{value[1]}
+ + )} + {group === REPORT_PARAMETERS.groups && ( + <> +
{name}
+ + )} +
+ ); + }} +
+
+ ); + })} + + + {formatMessage(labels.runQuery)} + + + + ); +} + +export default InsightsParameters; diff --git a/components/pages/reports/insights/InsightsParameters.module.css b/components/pages/reports/insights/InsightsParameters.module.css new file mode 100644 index 00000000..06b62414 --- /dev/null +++ b/components/pages/reports/insights/InsightsParameters.module.css @@ -0,0 +1,12 @@ +.parameter { + display: flex; + gap: 10px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + min-width: 0; +} + +.op { + font-weight: bold; +} diff --git a/components/pages/reports/insights/InsightsReport.js b/components/pages/reports/insights/InsightsReport.js new file mode 100644 index 00000000..88f12304 --- /dev/null +++ b/components/pages/reports/insights/InsightsReport.js @@ -0,0 +1,26 @@ +import Report from '../Report'; +import ReportHeader from '../ReportHeader'; +import ReportMenu from '../ReportMenu'; +import ReportBody from '../ReportBody'; +import InsightsParameters from './InsightsParameters'; +import InsightsTable from './InsightsTable'; +import Lightbulb from 'assets/lightbulb.svg'; + +const defaultParameters = { + type: 'insights', + parameters: { fields: [], filters: [], groups: [] }, +}; + +export default function InsightsReport({ reportId }) { + return ( + + } /> + + + + + + + + ); +} diff --git a/components/pages/reports/insights/InsightsTable.js b/components/pages/reports/insights/InsightsTable.js new file mode 100644 index 00000000..a767468e --- /dev/null +++ b/components/pages/reports/insights/InsightsTable.js @@ -0,0 +1,19 @@ +import { useContext } from 'react'; +import { GridTable, GridColumn } from 'react-basics'; +import { useMessages } from 'hooks'; +import { ReportContext } from '../Report'; + +export function InsightsTable() { + const { report } = useContext(ReportContext); + const { formatMessage, labels } = useMessages(); + + return ( + + + + + + ); +} + +export default InsightsTable; diff --git a/queries/analytics/eventData/getEventData.ts b/queries/analytics/eventData/getEventData.ts index 8a3b2927..2f3b04eb 100644 --- a/queries/analytics/eventData/getEventData.ts +++ b/queries/analytics/eventData/getEventData.ts @@ -40,16 +40,22 @@ async function clickhouseQuery( endDate: Date, criteria: EventDataCriteria, ) { - const { fields } = criteria; + const { fields, filters } = criteria; const { rawQuery, getDateFormat, getBetweenDates } = clickhouse; const website = await loadWebsite(websiteId); const resetDate = new Date(website?.resetAt || DEFAULT_CREATED_AT); - const uniqueFields = fields.reduce((obj, { name, type }) => { + const uniqueFields = fields.reduce((obj, { name, type, value }) => { + const prefix = type === 'array' ? 'string' : type; + if (!obj[name]) { obj[name] = { - columns: ['event_key as field', `count(*) as total`, `${type}_value as value`], - groups: ['event_key', `${type}_value`], + columns: [ + 'event_key as field', + `count(*) as total`, + value === 'unique' ? `${prefix}_value as value` : null, + ].filter(n => n), + groups: ['event_key', value === 'unique' ? `${prefix}_value` : null].filter(n => n), }; } return obj; @@ -69,7 +75,7 @@ async function clickhouseQuery( and created_at >= ${getDateFormat(resetDate)} and ${getBetweenDates('created_at', startDate, endDate)} group by ${field.groups.join(',')} - limit 20 + limit 100 `, params, ),