From fcb1767eb1309c37a1da1e81e6d447e0f3a60bb6 Mon Sep 17 00:00:00 2001 From: Brian Cao Date: Thu, 5 Jan 2023 19:39:29 -0800 Subject: [PATCH] Feat/um 145 re add events (#1733) * Re-add events. Fix event_type queries. * Re-add eventData * revert CSS. --- components/common/EventDataButton.js | 9 +- components/common/EventDataButton.module.css | 1 + components/forms/EventDataForm.js | 247 ++++++++---------- components/forms/EventDataForm.module.css | 7 +- components/metrics/FilterTags.js | 7 +- components/pages/WebsiteDetails.js | 44 +++- package.json | 3 +- pages/api/websites/[id]/metrics.ts | 2 +- .../analytics/pageview/getPageviewMetrics.ts | 22 +- queries/analytics/pageview/getPageviews.js | 2 +- .../analytics/session/getSessionMetrics.ts | 2 +- queries/analytics/stats/getWebsiteStats.ts | 2 +- yarn.lock | 41 ++- 13 files changed, 222 insertions(+), 167 deletions(-) diff --git a/components/common/EventDataButton.js b/components/common/EventDataButton.js index 706d07d1..66f06871 100644 --- a/components/common/EventDataButton.js +++ b/components/common/EventDataButton.js @@ -24,7 +24,7 @@ function EventDataButton({ websiteId }) { {showEventData && ( - }> - + } + onClose={handleClose} + > + {close => } )} diff --git a/components/common/EventDataButton.module.css b/components/common/EventDataButton.module.css index cd2a2ed6..af332bd2 100644 --- a/components/common/EventDataButton.module.css +++ b/components/common/EventDataButton.module.css @@ -1,3 +1,4 @@ .button { width: fit-content; + margin-bottom: 15px; } diff --git a/components/forms/EventDataForm.js b/components/forms/EventDataForm.js index e72e90f8..01013416 100644 --- a/components/forms/EventDataForm.js +++ b/components/forms/EventDataForm.js @@ -1,22 +1,26 @@ +import { useMutation } from '@tanstack/react-query'; import classNames from 'classnames'; -import { Button } from 'react-basics'; import DateFilter from 'components/common/DateFilter'; -import DropDown from 'components/common/DropDown'; -import FormLayout, { - FormButtons, - FormError, - FormMessage, - FormRow, -} from 'components/layout/FormLayout'; import DataTable from 'components/metrics/DataTable'; import FilterTags from 'components/metrics/FilterTags'; -import { Field, Form, Formik } from 'formik'; import useApi from 'hooks/useApi'; import useDateRange from 'hooks/useDateRange'; -import { useState, useEffect } from 'react'; -import { FormattedMessage } from 'react-intl'; -import styles from './EventDataForm.module.css'; import useTimezone from 'hooks/useTimezone'; +import { useEffect, useState, useRef } from 'react'; +import { + Button, + Dropdown, + Flexbox, + Form, + FormButtons, + FormInput, + FormRow, + Item, + TextField, +} from 'react-basics'; +import { FormattedMessage } from 'react-intl'; +import { FormMessage } from './../layout/FormLayout'; +import styles from './EventDataForm.module.css'; export const filterOptions = [ { label: 'Count', value: 'count' }, @@ -77,13 +81,17 @@ export const dateOptions = [ export default function EventDataForm({ websiteId, onClose, className }) { const { post } = useApi(); const [message, setMessage] = useState(); + const { mutate } = useMutation(data => post(`/websites/${websiteId}/eventdata`, data)); const [columns, setColumns] = useState({}); const [filters, setFilters] = useState({}); + const [type, setType] = useState(''); const [data, setData] = useState([]); const [dateRange, setDateRange] = useDateRange('report'); const { startDate, endDate, value } = dateRange; const [timezone] = useTimezone(); const [isValid, setIsValid] = useState(false); + const columnRef = useRef(null); + const filterRef = useRef(null); useEffect(() => { if (Object.keys(columns).length > 0) { @@ -93,9 +101,14 @@ export default function EventDataForm({ websiteId, onClose, className }) { } }, [columns]); - const handleAddTag = (value, list, setState, resetForm) => { + const handleAddTag = (value, list, setState, ref, clearDropdown) => { setState({ ...list, [`${value.field}`]: value.value }); - resetForm(); + + ref.current.reset({ field: '', value: '' }); + + if (clearDropdown) { + setType(''); + } }; const handleRemoveTag = (value, list, setState) => { @@ -116,134 +129,102 @@ export default function EventDataForm({ websiteId, onClose, className }) { filters, }; - const { ok, data } = await post(`/websites/${websiteId}/eventdata`, params); - - if (!ok) { - setMessage(); - setData([]); - } else { - setData(data); - setMessage(null); - } + mutate(params, { + onSuccess: async data => { + setData(data); + setMessage(null); + }, + onError: async () => { + setMessage( + , + ); + setData([]); + }, + }); }; return ( <> - {message} + + {message} +
- -
- - - +
+ }> + + +
+
+
+ handleAddTag({ ...value, value: type }, columns, setColumns, columnRef, true) + } + > + } + rules={{ required: 'Required' }} + > + + + } + > + + {({ value, label }) => {label}} + -
-
- - handleAddTag(value, columns, setColumns, resetForm) - } + + + + + handleRemoveTag(value, columns, setColumns)} + /> +
+
+
handleAddTag(value, filters, setFilters, filterRef)} + > + } > - {({ values, setFieldValue }) => ( - - - -
- - -
-
- - -
- setFieldValue('value', value)} - className={styles.dropdown} - name="value" - options={filterOptions} - /> - -
-
- - - - - )} - - handleRemoveTag(value, columns, setColumns)} - /> -
-
- - handleAddTag(value, filters, setFilters, resetForm) - } + + + } > - {({ values }) => ( -
- - -
- - -
-
- - -
- - -
-
- - - -
- )} -
- handleRemoveTag(value, filters, setFilters)} - /> -
- + + + + + + + + handleRemoveTag(value, filters, setFilters)} + /> +
diff --git a/components/forms/EventDataForm.module.css b/components/forms/EventDataForm.module.css index fd0ad290..c78eb290 100644 --- a/components/forms/EventDataForm.module.css +++ b/components/forms/EventDataForm.module.css @@ -3,8 +3,9 @@ } .form { + padding: 20px; border-right: 1px solid var(--base300); - width: 420px; + width: 425px; } .filters { @@ -16,6 +17,10 @@ min-height: 250px; } +.message { + width: 100%; +} + .table { padding: 10px; min-height: 430px; diff --git a/components/metrics/FilterTags.js b/components/metrics/FilterTags.js index 1fb27271..315d2a22 100644 --- a/components/metrics/FilterTags.js +++ b/components/metrics/FilterTags.js @@ -1,6 +1,6 @@ import classNames from 'classnames'; import { safeDecodeURI } from 'next-basics'; -import { Button } from 'react-basics'; +import { Button, Icon } from 'react-basics'; import Times from 'assets/times.svg'; import styles from './FilterTags.module.css'; @@ -16,8 +16,11 @@ export default function FilterTags({ className, params, onClick }) { } return (
-
); diff --git a/components/pages/WebsiteDetails.js b/components/pages/WebsiteDetails.js index 44412a5f..88fa72f8 100644 --- a/components/pages/WebsiteDetails.js +++ b/components/pages/WebsiteDetails.js @@ -1,26 +1,29 @@ -import { useState } from 'react'; -import { Column, Loading } from 'react-basics'; -import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import Arrow from 'assets/arrow-right.svg'; import classNames from 'classnames'; -import WebsiteChart from 'components/metrics/WebsiteChart'; +import Link from 'components/common/Link'; import WorldMap from 'components/common/WorldMap'; -import Page from 'components/layout/Page'; import GridRow from 'components/layout/GridRow'; import MenuLayout from 'components/layout/MenuLayout'; -import Link from 'components/common/Link'; -import PagesTable from 'components/metrics/PagesTable'; -import ReferrersTable from 'components/metrics/ReferrersTable'; +import Page from 'components/layout/Page'; import BrowsersTable from 'components/metrics/BrowsersTable'; -import OSTable from 'components/metrics/OSTable'; -import DevicesTable from 'components/metrics/DevicesTable'; import CountriesTable from 'components/metrics/CountriesTable'; +import DevicesTable from 'components/metrics/DevicesTable'; import LanguagesTable from 'components/metrics/LanguagesTable'; -import ScreenTable from 'components/metrics/ScreenTable'; +import OSTable from 'components/metrics/OSTable'; +import PagesTable from 'components/metrics/PagesTable'; import QueryParametersTable from 'components/metrics/QueryParametersTable'; -import usePageQuery from 'hooks/usePageQuery'; +import ReferrersTable from 'components/metrics/ReferrersTable'; +import ScreenTable from 'components/metrics/ScreenTable'; +import WebsiteChart from 'components/metrics/WebsiteChart'; import useApi from 'hooks/useApi'; +import usePageQuery from 'hooks/usePageQuery'; import { DEFAULT_ANIMATION_DURATION } from 'lib/constants'; -import Arrow from 'assets/arrow-right.svg'; +import { useState } from 'react'; +import { Column, Loading } from 'react-basics'; +import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; +import EventDataButton from './../common/EventDataButton'; +import EventsChart from './../metrics/EventsChart'; +import EventsTable from './../metrics/EventsTable'; import styles from './WebsiteDetails.module.css'; const messages = defineMessages({ @@ -32,6 +35,7 @@ const messages = defineMessages({ devices: { id: 'metrics.devices', defaultMessage: 'Devices' }, countries: { id: 'metrics.countries', defaultMessage: 'Countries' }, languages: { id: 'metrics.languages', defaultMessage: 'Languages' }, + events: { id: 'metrics.events', defaultMessage: 'Events' }, query: { id: 'metrics.query-parameters', defaultMessage: 'Query parameters' }, }); @@ -44,6 +48,7 @@ const views = { screen: ScreenTable, country: CountriesTable, language: LanguagesTable, + event: EventsTable, query: QueryParametersTable, }; @@ -104,6 +109,10 @@ export default function WebsiteDetails({ websiteId }) { label: formatMessage(messages.screens), value: resolve({ view: 'screen' }), }, + { + label: formatMessage(messages.events), + value: resolve({ view: 'event' }), + }, { label: formatMessage(messages.query), value: resolve({ view: 'query' }), @@ -172,6 +181,15 @@ export default function WebsiteDetails({ websiteId }) { + + + + + + + + + )} {view && chartLoaded && ( diff --git a/package.json b/package.json index f28eb038..492aee24 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "npm-run-all": "^4.1.5", "prop-types": "^15.7.2", "react": "^18.2.0", - "react-basics": "^0.44.0", + "react-basics": "^0.48.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-intl": "^5.24.7", @@ -109,6 +109,7 @@ "thenby": "^1.3.4", "timezone-support": "^2.0.2", "uuid": "^8.3.2", + "yup": "^0.32.11", "zustand": "^3.7.2" }, "devDependencies": { diff --git a/pages/api/websites/[id]/metrics.ts b/pages/api/websites/[id]/metrics.ts index 25759531..c6375f08 100644 --- a/pages/api/websites/[id]/metrics.ts +++ b/pages/api/websites/[id]/metrics.ts @@ -139,8 +139,8 @@ export default async ( startDate, endDate, column, - table, filters, + type, }); return ok(res, data); diff --git a/queries/analytics/pageview/getPageviewMetrics.ts b/queries/analytics/pageview/getPageviewMetrics.ts index 1e619fb8..8b82721c 100644 --- a/queries/analytics/pageview/getPageviewMetrics.ts +++ b/queries/analytics/pageview/getPageviewMetrics.ts @@ -12,8 +12,8 @@ export async function getPageviewMetrics( startDate: Date; endDate: Date; column: Prisma.WebsiteEventScalarFieldEnum | Prisma.SessionScalarFieldEnum; - table: string; filters: object; + type: string; }, ] ) { @@ -30,11 +30,16 @@ async function relationalQuery( endDate: Date; column: Prisma.WebsiteEventScalarFieldEnum | Prisma.SessionScalarFieldEnum; filters: object; + type: string; }, ) { - const { startDate, endDate, column, filters = {} } = data; + const { startDate, endDate, column, filters = {}, type } = data; const { rawQuery, parseFilters } = prisma; - const params = [startDate, endDate]; + const params: any = [ + startDate, + endDate, + type === 'event' ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView, + ]; const { filterQuery, joinSession } = parseFilters(filters, params); return rawQuery( @@ -43,7 +48,7 @@ async function relationalQuery( ${joinSession} where website_id='${websiteId}' and website_event.created_at between $1 and $2 - and event_type = ${EVENT_TYPE.pageView} + and event_type = $3 ${filterQuery} group by 1 order by 2 desc`, @@ -58,12 +63,17 @@ async function clickhouseQuery( endDate: Date; column: Prisma.WebsiteEventScalarFieldEnum | Prisma.SessionScalarFieldEnum; filters: object; + type: string; }, ) { - const { startDate, endDate, column, filters = {} } = data; + const { startDate, endDate, column, filters = {}, type } = data; const { rawQuery, parseFilters, getBetweenDates } = clickhouse; const website = await cache.fetchWebsite(websiteId); - const params = [websiteId, website?.revId || 0, EVENT_TYPE.pageView]; + const params = [ + websiteId, + website?.revId || 0, + type === 'event' ? EVENT_TYPE.customEvent : EVENT_TYPE.pageView, + ]; const { filterQuery } = parseFilters(filters, params); return rawQuery( diff --git a/queries/analytics/pageview/getPageviews.js b/queries/analytics/pageview/getPageviews.js index 03f8a485..7854e13e 100644 --- a/queries/analytics/pageview/getPageviews.js +++ b/queries/analytics/pageview/getPageviews.js @@ -32,7 +32,7 @@ async function clickhouseQuery(websites, startAt) { created_at, url from event - where event_name = '' + where event_type = 1 and ${ websites && websites.length > 0 ? `website_id in (${getCommaSeparatedStringFormat(websites)})` diff --git a/queries/analytics/session/getSessionMetrics.ts b/queries/analytics/session/getSessionMetrics.ts index 04e1801a..cbaf235e 100644 --- a/queries/analytics/session/getSessionMetrics.ts +++ b/queries/analytics/session/getSessionMetrics.ts @@ -58,7 +58,7 @@ async function clickhouseQuery( from event as x where website_id = $1 and rev_id = $2 - and event_name = '' + and event_type = 1 and ${getBetweenDates('created_at', startDate, endDate)} ${filterQuery} group by x diff --git a/queries/analytics/stats/getWebsiteStats.ts b/queries/analytics/stats/getWebsiteStats.ts index bf5cdd96..779a67b8 100644 --- a/queries/analytics/stats/getWebsiteStats.ts +++ b/queries/analytics/stats/getWebsiteStats.ts @@ -67,7 +67,7 @@ async function clickhouseQuery( min(created_at) min_time, max(created_at) max_time from event - where event_name = '' + where event_type = 1 and website_id = $1 and rev_id = $2 and ${getBetweenDates('created_at', startDate, endDate)} diff --git a/yarn.lock b/yarn.lock index 450c4744..c3807215 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2176,6 +2176,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/lodash@^4.14.175": + version "4.14.191" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.191.tgz#09511e7f7cba275acd8b419ddac8da9a6a79e2fa" + integrity sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ== + "@types/minimist@^1.2.0": version "1.2.2" resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" @@ -5545,6 +5550,11 @@ murmurhash@^2.0.0: resolved "https://registry.yarnpkg.com/murmurhash/-/murmurhash-2.0.1.tgz#4097720e08cf978872194ad84ea5be2dec9b610f" integrity sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew== +nanoclone@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" + integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== + nanoid@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" @@ -6353,6 +6363,11 @@ prop-types@^15.7.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +property-expr@^2.0.4: + version "2.0.5" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.5.tgz#278bdb15308ae16af3e3b9640024524f4dc02cb4" + integrity sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA== + psl@^1.1.24: version "1.9.0" resolved "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz" @@ -6423,10 +6438,10 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-basics@^0.44.0: - version "0.44.0" - resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.44.0.tgz#5c022da288d8a3da6b32111704acf1113e04b15e" - integrity sha512-O8W/zPCRHDGKfyGDLBZBMXE9Q2EkK+hUD+bdkUNMLQsDq1ON15uOKwlcUff2lIjwi1LxHuq7Dbwrdn6yxXH4JQ== +react-basics@^0.48.0: + version "0.48.0" + resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.48.0.tgz#f86924b6e0fbe67e55e3c27763390f4d3796edb4" + integrity sha512-0pbPsylQevm08VGY7aqqToLh9Xl764FNDPp6NxnwHlJSL9jvYRxhW2ajivsZOfeIPJgRiWbmmEBGmTmSwwhiIQ== dependencies: classnames "^2.3.1" react "^18.2.0" @@ -7572,6 +7587,11 @@ topojson-client@^3.1.0: dependencies: commander "2" +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== + tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz" @@ -8013,6 +8033,19 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +yup@^0.32.11: + version "0.32.11" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5" + integrity sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg== + dependencies: + "@babel/runtime" "^7.15.4" + "@types/lodash" "^4.14.175" + lodash "^4.17.21" + lodash-es "^4.17.21" + nanoclone "^0.2.1" + property-expr "^2.0.4" + toposort "^2.0.2" + zustand@^3.7.2: version "3.7.2" resolved "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz"