diff --git a/components/messages.js b/components/messages.js index 286a5788..b8605126 100644 --- a/components/messages.js +++ b/components/messages.js @@ -77,7 +77,7 @@ export const labels = defineMessages({ referrers: { id: 'label.referrers', defaultMessage: 'Referrers' }, screens: { id: 'label.screens', defaultMessage: 'Screens' }, browsers: { id: 'label.browsers', defaultMessage: 'Browsers' }, - os: { id: 'label.operating-systems', defaultMessage: 'Operating systems' }, + os: { id: 'label.os', defaultMessage: 'OS' }, devices: { id: 'label.devices', defaultMessage: 'Devices' }, countries: { id: 'label.countries', defaultMessage: 'Countries' }, languages: { id: 'label.languages', defaultMessage: 'Languages' }, @@ -133,33 +133,35 @@ export const labels = defineMessages({ runQuery: { id: 'label.run-query', defaultMessage: 'Run query' }, field: { id: 'label.field', defaultMessage: 'Field' }, fields: { id: 'label.fields', defaultMessage: 'Fields' }, - createReport: { id: 'labels.create-report', defaultMessage: 'Create report' }, - description: { id: 'labels.description', defaultMessage: 'Description' }, - untitled: { id: 'labels.untitled', defaultMessage: 'Untitled' }, - type: { id: 'labels.type', defaultMessage: 'Type' }, - filters: { id: 'labels.filters', defaultMessage: 'Filters' }, - breakdown: { id: 'labels.breakdown', defaultMessage: 'Breakdown' }, - true: { id: 'labels.true', defaultMessage: 'True' }, - false: { id: 'labels.false', defaultMessage: 'False' }, - equals: { id: 'labels.equals', defaultMessage: 'Equals' }, - doesNotEqual: { id: 'labels.does-not-equal', defaultMessage: 'Does not equal' }, - greaterThan: { id: 'labels.greater-than', defaultMessage: 'Greater than' }, - lessThan: { id: 'labels.less-than', defaultMessage: 'Less than' }, - greaterThanEquals: { id: 'labels.greater-than-equals', defaultMessage: 'Greater than or equals' }, - lessThanEquals: { id: 'labels.less-than-equals', defaultMessage: 'Less than or equals' }, - contains: { id: 'labels.contains', defaultMessage: 'Contains' }, - doesNotContain: { id: 'labels.does-not-contain', defaultMessage: 'Does not contain' }, - before: { id: 'labels.before', defaultMessage: 'Before' }, - after: { id: 'labels.after', defaultMessage: 'After' }, - total: { id: 'labels.total', defaultMessage: 'Total' }, - sum: { id: 'labels.sum', defaultMessage: 'Sum' }, - average: { id: 'labels.average', defaultMessage: 'Average' }, - min: { id: 'labels.min', defaultMessage: 'Min' }, - max: { id: 'labels.max', defaultMessage: 'Max' }, - unique: { id: 'labels.unique', defaultMessage: 'Unique' }, - value: { id: 'labels.value', defaultMessage: 'Value' }, - overview: { id: 'labels.overview', defaultMessage: 'Overview' }, - totalRecords: { id: 'labels.total-records', defaultMessage: 'Total records' }, + createReport: { id: 'label.create-report', defaultMessage: 'Create report' }, + description: { id: 'label.description', defaultMessage: 'Description' }, + untitled: { id: 'label.untitled', defaultMessage: 'Untitled' }, + type: { id: 'label.type', defaultMessage: 'Type' }, + filters: { id: 'label.filters', defaultMessage: 'Filters' }, + breakdown: { id: 'label.breakdown', defaultMessage: 'Breakdown' }, + true: { id: 'label.true', defaultMessage: 'True' }, + false: { id: 'label.false', defaultMessage: 'False' }, + is: { id: 'label.is', defaultMessage: 'Is' }, + isNot: { id: 'label.is-not', defaultMessage: 'Is not' }, + isSet: { id: 'label.is-set', defaultMessage: 'Is set' }, + isNotSet: { id: 'label.is-not-set', defaultMessage: 'Is not set' }, + greaterThan: { id: 'label.greater-than', defaultMessage: 'Greater than' }, + lessThan: { id: 'label.less-than', defaultMessage: 'Less than' }, + greaterThanEquals: { id: 'label.greater-than-equals', defaultMessage: 'Greater than or equals' }, + lessThanEquals: { id: 'label.less-than-equals', defaultMessage: 'Less than or equals' }, + contains: { id: 'label.contains', defaultMessage: 'Contains' }, + doesNotContain: { id: 'label.does-not-contain', defaultMessage: 'Does not contain' }, + before: { id: 'label.before', defaultMessage: 'Before' }, + after: { id: 'label.after', defaultMessage: 'After' }, + total: { id: 'label.total', defaultMessage: 'Total' }, + sum: { id: 'label.sum', defaultMessage: 'Sum' }, + average: { id: 'label.average', defaultMessage: 'Average' }, + min: { id: 'label.min', defaultMessage: 'Min' }, + max: { id: 'label.max', defaultMessage: 'Max' }, + unique: { id: 'label.unique', defaultMessage: 'Unique' }, + value: { id: 'label.value', defaultMessage: 'Value' }, + overview: { id: 'label.overview', defaultMessage: 'Overview' }, + totalRecords: { id: 'label.total-records', defaultMessage: 'Total records' }, insights: { id: 'label.insights', defaultMessage: 'Insights' }, dropoff: { id: 'label.dropoff', defaultMessage: 'Dropoff' }, referrer: { id: 'label.referrer', defaultMessage: 'Referrer' }, diff --git a/components/pages/reports/FieldFilterForm.js b/components/pages/reports/FieldFilterForm.js index a2b68968..381bb7e3 100644 --- a/components/pages/reports/FieldFilterForm.js +++ b/components/pages/reports/FieldFilterForm.js @@ -1,17 +1,28 @@ import { useState } from 'react'; import { Form, FormRow, Item, Flexbox, Dropdown, Button } from 'react-basics'; -import { useFilters } from 'hooks'; +import { useMessages, useFilters, useFormat } from 'hooks'; import styles from './FieldFilterForm.module.css'; -export default function FieldFilterForm({ label, type, values, onSelect }) { +export default function FieldFilterForm({ name, label, type, values, onSelect }) { + const { formatMessage, labels } = useMessages(); const [filter, setFilter] = useState('eq'); const [value, setValue] = useState(); - const filters = useFilters(type); + const { getFilters } = useFilters(); + const { formatValue } = useFormat(); + const filters = getFilters(type); const renderFilterValue = value => { return filters.find(f => f.value === value)?.label; }; + const renderValue = value => { + return formatValue(value, name); + }; + + const handleAdd = () => { + onSelect({ name, type, filter, value }); + }; + return (
@@ -27,18 +38,20 @@ export default function FieldFilterForm({ label, type, values, onSelect }) { return {label}; }} - + {value => { - return {value}; + return {formatValue(value, name)}; }} -
diff --git a/components/pages/reports/FilterSelectForm.js b/components/pages/reports/FilterSelectForm.js index 49238e1c..844c2a1d 100644 --- a/components/pages/reports/FilterSelectForm.js +++ b/components/pages/reports/FilterSelectForm.js @@ -27,8 +27,16 @@ export default function FilterSelectForm({ websiteId, items, onSelect }) { } if (isLoading) { - return ; + return ; } - return ; + return ( + + ); } diff --git a/components/pages/reports/insights/InsightsParameters.js b/components/pages/reports/insights/InsightsParameters.js index c6140d68..d4c0b95b 100644 --- a/components/pages/reports/insights/InsightsParameters.js +++ b/components/pages/reports/insights/InsightsParameters.js @@ -1,6 +1,15 @@ import { useContext, useRef } from 'react'; -import { useMessages } from 'hooks'; -import { Form, FormRow, FormButtons, SubmitButton, PopupTrigger, Icon, Popup } from 'react-basics'; +import { useFormat, useMessages, useFilters } from 'hooks'; +import { + Form, + FormRow, + FormButtons, + SubmitButton, + PopupTrigger, + Icon, + Popup, + TooltipPopup, +} from 'react-basics'; import { ReportContext } from 'components/pages/reports/Report'; import Icons from 'components/icons'; import BaseParameters from '../BaseParameters'; @@ -13,23 +22,26 @@ import FieldSelectForm from '../FieldSelectForm'; export function InsightsParameters() { const { report, runReport, updateReport, isRunning } = useContext(ReportContext); const { formatMessage, labels } = useMessages(); + const { formatValue } = useFormat(); + const { filterLabels } = useFilters(); const ref = useRef(null); const { parameters } = report || {}; const { websiteId, dateRange, fields, filters } = parameters || {}; + const { startDate, endDate } = dateRange || {}; + const parametersSelected = websiteId && startDate && endDate; const queryEnabled = websiteId && dateRange && (fields?.length || filters?.length); const fieldOptions = [ - { name: 'url_path', label: formatMessage(labels.url) }, - { name: 'page_title', label: formatMessage(labels.pageTitle) }, - { name: 'referrer_domain', label: formatMessage(labels.referrer) }, - { name: 'url_query', label: formatMessage(labels.query) }, + { name: 'url', label: formatMessage(labels.url) }, + { name: 'title', label: formatMessage(labels.pageTitle) }, + { name: 'referrer', label: formatMessage(labels.referrer) }, + { name: 'query', label: formatMessage(labels.query) }, { name: 'browser', label: formatMessage(labels.browser) }, { name: 'os', label: formatMessage(labels.os) }, { name: 'device', label: formatMessage(labels.device) }, { name: 'country', label: formatMessage(labels.country) }, { name: 'region', label: formatMessage(labels.region) }, { name: 'city', label: formatMessage(labels.city) }, - { name: 'language', label: formatMessage(labels.language) }, ]; const parameterGroups = [ @@ -63,9 +75,11 @@ export function InsightsParameters() { const AddButton = ({ id }) => { return ( - - - + + + + + {(close, element) => { return ( @@ -91,32 +105,33 @@ export function InsightsParameters() { return (
- {parameterGroups.map(({ id, label }) => { - return ( - }> - handleRemove(id, index)}> - {({ value, label }) => { - return ( -
- {id === 'fields' && ( - <> -
{label}
- - )} - {id === 'filters' && ( - <> -
{label}
-
{value[0]}
-
{value[1]}
- - )} -
- ); - }} -
-
- ); - })} + {parametersSelected && + parameterGroups.map(({ id, label }) => { + return ( + }> + handleRemove(id, index)}> + {({ name, filter, value, label }) => { + return ( +
+ {id === 'fields' && ( + <> +
{label}
+ + )} + {id === 'filters' && ( + <> +
{fieldOptions.find(f => f.name === name)?.label}
+
{filterLabels[filter]}
+
{formatValue(value, name)}
+ + )} +
+ ); + }} +
+
+ ); + })} {formatMessage(labels.runQuery)} diff --git a/hooks/useFilters.js b/hooks/useFilters.js index 0175e72a..5143fe5b 100644 --- a/hooks/useFilters.js +++ b/hooks/useFilters.js @@ -1,11 +1,13 @@ import { useMessages } from 'hooks'; -export function useFilters(type) { +export function useFilters() { const { formatMessage, labels } = useMessages(); - const filters = { - eq: formatMessage(labels.equals), - neq: formatMessage(labels.doesNotEqual), + const filterLabels = { + eq: formatMessage(labels.is), + neq: formatMessage(labels.isNot), + s: formatMessage(labels.isSet), + ns: formatMessage(labels.isNotSet), c: formatMessage(labels.contains), dnc: formatMessage(labels.doesNotContain), t: formatMessage(labels.true), @@ -18,7 +20,7 @@ export function useFilters(type) { af: formatMessage(labels.after), }; - const types = { + const typeFilters = { string: ['eq', 'neq'], array: ['c', 'dnc'], boolean: ['t', 'f'], @@ -27,7 +29,11 @@ export function useFilters(type) { uuid: ['eq'], }; - return types[type]?.map(key => ({ type, value: key, label: filters[key] })) ?? []; + const getFilters = type => { + return typeFilters[type]?.map(key => ({ type, value: key, label: filterLabels[key] })) ?? []; + }; + + return { getFilters, filterLabels, typeFilters }; } export default useFilters; diff --git a/pages/api/websites/[id]/values.ts b/pages/api/websites/[id]/values.ts index b40fc262..ad8625bd 100644 --- a/pages/api/websites/[id]/values.ts +++ b/pages/api/websites/[id]/values.ts @@ -3,7 +3,7 @@ import { canViewWebsite } from 'lib/auth'; import { useAuth, useCors } from 'lib/middleware'; import { NextApiResponse } from 'next'; import { badRequest, methodNotAllowed, ok, unauthorized } from 'next-basics'; -import { EVENT_COLUMNS, SESSION_COLUMNS } from 'lib/constants'; +import { EVENT_COLUMNS, FILTER_COLUMNS, SESSION_COLUMNS } from 'lib/constants'; import { getValues } from 'queries'; export interface WebsiteResetRequestQuery { @@ -28,7 +28,7 @@ export default async ( return unauthorized(res); } - const values = await getValues(websiteId, type as string); + const values = await getValues(websiteId, FILTER_COLUMNS[type as string]); return ok( res, diff --git a/queries/analytics/reports/getInsights.ts b/queries/analytics/reports/getInsights.ts index 2167aa2c..9793f258 100644 --- a/queries/analytics/reports/getInsights.ts +++ b/queries/analytics/reports/getInsights.ts @@ -45,7 +45,7 @@ async function relationalQuery( and website_event.created_at between {{startDate}} and {{endDate}} and website_event.event_type = {{eventType}} ${filterQuery} - group by ${fields.map(({ name }) => name).join(',')} + ${parseGroupBy(fields)} order by 1 desc, 2 desc limit 500 `, @@ -78,7 +78,7 @@ async function clickhouseQuery( and created_at between {startDate:DateTime} and {endDate:DateTime} and event_type = {eventType:UInt32} ${filterQuery} - group by ${fields.map(({ name }) => name).join(',')} + ${parseGroupBy(fields)} order by 1 desc, 2 desc limit 500 `, @@ -98,3 +98,10 @@ function parseFields(fields) { return query.join(',\n'); } + +function parseGroupBy(fields) { + if (!fields.length) { + return ''; + } + return `group by ${fields.map(({ name }) => name).join(',')}`; +}