From 7df142b02b31afee1576ce55ae887ea6aa9b8695 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Wed, 16 Aug 2023 12:12:45 -0700 Subject: [PATCH 1/6] reorder getfunnel where to match index --- queries/analytics/reports/getFunnel.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/queries/analytics/reports/getFunnel.ts b/queries/analytics/reports/getFunnel.ts index 3c5c65e0..1bbbc878 100644 --- a/queries/analytics/reports/getFunnel.ts +++ b/queries/analytics/reports/getFunnel.ts @@ -57,12 +57,14 @@ async function relationalQuery( from level${i} l join website_event we on l.session_id = we.session_id - where we.created_at between l.created_at - and ${getAddMinutesQuery(`l.created_at `, windowMinutes)} + where we.website_id = {{websiteId::uuid}} + and we.created_at between l.created_at and ${getAddMinutesQuery( + `l.created_at `, + windowMinutes, + )} and we.referrer_path = {{${i - 1}}} and we.url_path = {{${i}}} and we.created_at <= {{endDate}} - and we.website_id = {{websiteId::uuid}} )`; } From d6c8f3aa18bdcfe6a46c2397819ddb45424cb03c Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Wed, 16 Aug 2023 13:47:43 -0700 Subject: [PATCH 2/6] fix mapfilter / rawquery for relational --- lib/clickhouse.ts | 2 +- lib/prisma.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/clickhouse.ts b/lib/clickhouse.ts index aa2f21ed..8ce7bc98 100644 --- a/lib/clickhouse.ts +++ b/lib/clickhouse.ts @@ -66,7 +66,7 @@ function getDateFormat(date) { function mapFilter(column, operator, name, type = 'String') { switch (operator) { case OPERATORS.equals: - return `${column} = {${name}:${type}`; + return `${column} = {${name}:${type}}`; case OPERATORS.notEquals: return `${column} != {${name}:${type}}`; default: diff --git a/lib/prisma.ts b/lib/prisma.ts index cebd7193..12bafa51 100644 --- a/lib/prisma.ts +++ b/lib/prisma.ts @@ -92,12 +92,12 @@ function getTimestampIntervalQuery(field: string): string { } } -function mapFilter(column, operator, name, type = 'String') { +function mapFilter(column, operator, name, type = 'varchar') { switch (operator) { case OPERATORS.equals: - return `${column} = {${name}:${type}`; + return `${column} = {{${name}::${type}}}`; case OPERATORS.notEquals: - return `${column} != {${name}:${type}}`; + return `${column} != {{${name}::${type}}}`; default: return ''; } @@ -161,7 +161,7 @@ async function rawQuery(sql: string, data: object): Promise { return Promise.reject(new Error('Unknown database.')); } - const query = sql?.replaceAll(/\{\{\s*(\w+)(::\w+)?\s*}}/g, (...args) => { + const query = sql?.replaceAll(/\{\{\s*(\w+)(::\w+)?\s*\}\}/g, (...args) => { const [, name, type] = args; params.push(data[name]); From 146650586b3cbd4d484e321c15d45c7c4baa0109 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Wed, 16 Aug 2023 13:56:41 -0700 Subject: [PATCH 3/6] add normalize filters to clickhouse --- lib/clickhouse.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/clickhouse.ts b/lib/clickhouse.ts index 8ce7bc98..bc10a6d4 100644 --- a/lib/clickhouse.ts +++ b/lib/clickhouse.ts @@ -94,13 +94,23 @@ function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {}) return query.join('\n'); } +function normalizeFilters(filters = {}) { + return Object.keys(filters).reduce((obj, key) => { + const value = filters[key]; + + obj[key] = value?.value ?? value; + + return obj; + }, {}); +} + async function parseFilters(websiteId: string, filters: QueryFilters = {}, options?: QueryOptions) { const website = await loadWebsite(websiteId); return { filterQuery: getFilterQuery(filters, options), params: { - ...filters, + ...normalizeFilters(filters), websiteId, startDate: maxDate(filters.startDate, website.resetAt), websiteDomain: website.domain, From 6dba68c823df80302fc261b3a5d854a660d7cade Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Wed, 16 Aug 2023 14:33:55 -0700 Subject: [PATCH 4/6] add metric performance indexes --- .../03_metric_performance_index/migration.sql | 50 +++++++++++++++++++ db/mysql/schema.prisma | 17 +++++++ .../03_metric_performance_index/migration.sql | 50 +++++++++++++++++++ db/postgresql/schema.prisma | 17 +++++++ 4 files changed, 134 insertions(+) create mode 100644 db/mysql/migrations/03_metric_performance_index/migration.sql create mode 100644 db/postgresql/migrations/03_metric_performance_index/migration.sql diff --git a/db/mysql/migrations/03_metric_performance_index/migration.sql b/db/mysql/migrations/03_metric_performance_index/migration.sql new file mode 100644 index 00000000..64681364 --- /dev/null +++ b/db/mysql/migrations/03_metric_performance_index/migration.sql @@ -0,0 +1,50 @@ +-- CreateIndex +CREATE INDEX `event_data_website_id_created_at_idx` ON `event_data`(`website_id`, `created_at`); + +-- CreateIndex +CREATE INDEX `event_data_website_id_created_at_event_key_idx` ON `event_data`(`website_id`, `created_at`, `event_key`); + +-- CreateIndex +CREATE INDEX `session_website_id_created_at_idx` ON `session`(`website_id`, `created_at`); + +-- CreateIndex +CREATE INDEX `session_website_id_created_at_hostname_idx` ON `session`(`website_id`, `created_at`, `hostname`); + +-- CreateIndex +CREATE INDEX `session_website_id_created_at_browser_idx` ON `session`(`website_id`, `created_at`, `browser`); + +-- CreateIndex +CREATE INDEX `session_website_id_created_at_os_idx` ON `session`(`website_id`, `created_at`, `os`); + +-- CreateIndex +CREATE INDEX `session_website_id_created_at_device_idx` ON `session`(`website_id`, `created_at`, `device`); + +-- CreateIndex +CREATE INDEX `session_website_id_created_at_screen_idx` ON `session`(`website_id`, `created_at`, `screen`); + +-- CreateIndex +CREATE INDEX `session_website_id_created_at_language_idx` ON `session`(`website_id`, `created_at`, `language`); + +-- CreateIndex +CREATE INDEX `session_website_id_created_at_country_idx` ON `session`(`website_id`, `created_at`, `country`); + +-- CreateIndex +CREATE INDEX `session_website_id_created_at_subdivision1_idx` ON `session`(`website_id`, `created_at`, `subdivision1`); + +-- CreateIndex +CREATE INDEX `session_website_id_created_at_city_idx` ON `session`(`website_id`, `created_at`, `city`); + +-- CreateIndex +CREATE INDEX `website_event_website_id_created_at_url_path_idx` ON `website_event`(`website_id`, `created_at`, `url_path`); + +-- CreateIndex +CREATE INDEX `website_event_website_id_created_at_url_query_idx` ON `website_event`(`website_id`, `created_at`, `url_query`); + +-- CreateIndex +CREATE INDEX `website_event_website_id_created_at_referrer_domain_idx` ON `website_event`(`website_id`, `created_at`, `referrer_domain`); + +-- CreateIndex +CREATE INDEX `website_event_website_id_created_at_page_title_idx` ON `website_event`(`website_id`, `created_at`, `page_title`); + +-- CreateIndex +CREATE INDEX `website_event_website_id_created_at_event_name_idx` ON `website_event`(`website_id`, `created_at`, `event_name`); diff --git a/db/mysql/schema.prisma b/db/mysql/schema.prisma index a25405df..38bb91f4 100644 --- a/db/mysql/schema.prisma +++ b/db/mysql/schema.prisma @@ -44,6 +44,16 @@ model Session { @@index([createdAt]) @@index([websiteId]) + @@index([websiteId, createdAt]) + @@index([websiteId, createdAt, hostname]) + @@index([websiteId, createdAt, browser]) + @@index([websiteId, createdAt, os]) + @@index([websiteId, createdAt, device]) + @@index([websiteId, createdAt, screen]) + @@index([websiteId, createdAt, language]) + @@index([websiteId, createdAt, country]) + @@index([websiteId, createdAt, subdivision1]) + @@index([websiteId, createdAt, city]) @@map("session") } @@ -91,6 +101,11 @@ model WebsiteEvent { @@index([sessionId]) @@index([websiteId]) @@index([websiteId, createdAt]) + @@index([websiteId, createdAt, urlPath]) + @@index([websiteId, createdAt, urlQuery]) + @@index([websiteId, createdAt, referrerDomain]) + @@index([websiteId, createdAt, pageTitle]) + @@index([websiteId, createdAt, eventName]) @@index([websiteId, sessionId, createdAt]) @@map("website_event") } @@ -113,6 +128,8 @@ model EventData { @@index([websiteId]) @@index([websiteEventId]) @@index([websiteId, websiteEventId, createdAt]) + @@index([websiteId, createdAt]) + @@index([websiteId, createdAt, eventKey]) @@map("event_data") } diff --git a/db/postgresql/migrations/03_metric_performance_index/migration.sql b/db/postgresql/migrations/03_metric_performance_index/migration.sql new file mode 100644 index 00000000..5db7aa50 --- /dev/null +++ b/db/postgresql/migrations/03_metric_performance_index/migration.sql @@ -0,0 +1,50 @@ +-- CreateIndex +CREATE INDEX "event_data_website_id_created_at_idx" ON "event_data"("website_id", "created_at"); + +-- CreateIndex +CREATE INDEX "event_data_website_id_created_at_event_key_idx" ON "event_data"("website_id", "created_at", "event_key"); + +-- CreateIndex +CREATE INDEX "session_website_id_created_at_idx" ON "session"("website_id", "created_at"); + +-- CreateIndex +CREATE INDEX "session_website_id_created_at_hostname_idx" ON "session"("website_id", "created_at", "hostname"); + +-- CreateIndex +CREATE INDEX "session_website_id_created_at_browser_idx" ON "session"("website_id", "created_at", "browser"); + +-- CreateIndex +CREATE INDEX "session_website_id_created_at_os_idx" ON "session"("website_id", "created_at", "os"); + +-- CreateIndex +CREATE INDEX "session_website_id_created_at_device_idx" ON "session"("website_id", "created_at", "device"); + +-- CreateIndex +CREATE INDEX "session_website_id_created_at_screen_idx" ON "session"("website_id", "created_at", "screen"); + +-- CreateIndex +CREATE INDEX "session_website_id_created_at_language_idx" ON "session"("website_id", "created_at", "language"); + +-- CreateIndex +CREATE INDEX "session_website_id_created_at_country_idx" ON "session"("website_id", "created_at", "country"); + +-- CreateIndex +CREATE INDEX "session_website_id_created_at_subdivision1_idx" ON "session"("website_id", "created_at", "subdivision1"); + +-- CreateIndex +CREATE INDEX "session_website_id_created_at_city_idx" ON "session"("website_id", "created_at", "city"); + +-- CreateIndex +CREATE INDEX "website_event_website_id_created_at_url_path_idx" ON "website_event"("website_id", "created_at", "url_path"); + +-- CreateIndex +CREATE INDEX "website_event_website_id_created_at_url_query_idx" ON "website_event"("website_id", "created_at", "url_query"); + +-- CreateIndex +CREATE INDEX "website_event_website_id_created_at_referrer_domain_idx" ON "website_event"("website_id", "created_at", "referrer_domain"); + +-- CreateIndex +CREATE INDEX "website_event_website_id_created_at_page_title_idx" ON "website_event"("website_id", "created_at", "page_title"); + +-- CreateIndex +CREATE INDEX "website_event_website_id_created_at_event_name_idx" ON "website_event"("website_id", "created_at", "event_name"); diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index 5753c6ef..d7a70ab0 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -44,6 +44,16 @@ model Session { @@index([createdAt]) @@index([websiteId]) + @@index([websiteId, createdAt]) + @@index([websiteId, createdAt, hostname]) + @@index([websiteId, createdAt, browser]) + @@index([websiteId, createdAt, os]) + @@index([websiteId, createdAt, device]) + @@index([websiteId, createdAt, screen]) + @@index([websiteId, createdAt, language]) + @@index([websiteId, createdAt, country]) + @@index([websiteId, createdAt, subdivision1]) + @@index([websiteId, createdAt, city]) @@map("session") } @@ -91,6 +101,11 @@ model WebsiteEvent { @@index([sessionId]) @@index([websiteId]) @@index([websiteId, createdAt]) + @@index([websiteId, createdAt, urlPath]) + @@index([websiteId, createdAt, urlQuery]) + @@index([websiteId, createdAt, referrerDomain]) + @@index([websiteId, createdAt, pageTitle]) + @@index([websiteId, createdAt, eventName]) @@index([websiteId, sessionId, createdAt]) @@map("website_event") } @@ -112,6 +127,8 @@ model EventData { @@index([createdAt]) @@index([websiteId]) @@index([websiteEventId]) + @@index([websiteId, createdAt]) + @@index([websiteId, createdAt, eventKey]) @@map("event_data") } From 9b8fa08d8235729cb23c901d1cf0f4a301f1d75d Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 17 Aug 2023 01:33:10 -0700 Subject: [PATCH 5/6] Fixed ugly navbar. --- components/layout/AppLayout.module.css | 1 + 1 file changed, 1 insertion(+) diff --git a/components/layout/AppLayout.module.css b/components/layout/AppLayout.module.css index 0afd11f9..be51f83c 100644 --- a/components/layout/AppLayout.module.css +++ b/components/layout/AppLayout.module.css @@ -10,6 +10,7 @@ width: 100vw; grid-column: 1; grid-row: 1 / 2; + z-index: 1; } .body { From 2c8996b68f8c7ea42e4c5d006249337957e15427 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 17 Aug 2023 03:21:20 -0700 Subject: [PATCH 6/6] Retention report UI updates. --- components/input/MonthSelect.js | 65 ++++++++++++------- components/input/MonthSelect.module.css | 10 +++ components/pages/reports/PopupForm.js | 26 ++------ components/pages/reports/PopupForm.module.css | 2 +- .../reports/insights/InsightsParameters.js | 8 +-- .../insights/InsightsParameters.module.css | 5 ++ .../reports/retention/RetentionParameters.js | 2 +- .../reports/retention/RetentionReport.js | 8 ++- .../pages/reports/retention/RetentionTable.js | 61 ++++++++--------- .../retention/RetentionTable.module.css | 6 +- package.json | 2 +- yarn.lock | 8 +-- 12 files changed, 110 insertions(+), 93 deletions(-) diff --git a/components/input/MonthSelect.js b/components/input/MonthSelect.js index bb054446..a20890ad 100644 --- a/components/input/MonthSelect.js +++ b/components/input/MonthSelect.js @@ -1,5 +1,13 @@ -import { useRef, useState } from 'react'; -import { Text, Icon, CalendarMonthSelect, CalendarYearSelect, Button } from 'react-basics'; +import { useRef } from 'react'; +import { + Text, + Icon, + CalendarMonthSelect, + CalendarYearSelect, + Button, + PopupTrigger, + Popup, +} from 'react-basics'; import { startOfMonth, endOfMonth } from 'date-fns'; import Icons from 'components/icons'; import { useLocale } from 'hooks'; @@ -7,43 +15,50 @@ import { formatDate } from 'lib/date'; import { getDateLocale } from 'lib/lang'; import styles from './MonthSelect.module.css'; -const MONTH = 'month'; -const YEAR = 'year'; - export function MonthSelect({ date = new Date(), onChange }) { const { locale } = useLocale(); - const [select, setSelect] = useState(null); const month = formatDate(date, 'MMMM', locale); const year = date.getFullYear(); const ref = useRef(); - const handleSelect = value => { - setSelect(state => (state !== value ? value : null)); - }; - const handleChange = date => { onChange(`range:${startOfMonth(date).getTime()}:${endOfMonth(date).getTime()}`); - setSelect(null); }; return ( <>
- - + + + + + + + + + + + +
- {select === MONTH && ( - - )} - {select === YEAR && ( - - )} ); } diff --git a/components/input/MonthSelect.module.css b/components/input/MonthSelect.module.css index 04cf575c..3b13bcc1 100644 --- a/components/input/MonthSelect.module.css +++ b/components/input/MonthSelect.module.css @@ -2,6 +2,8 @@ display: flex; align-items: center; justify-content: center; + border: 1px solid var(--base400); + border-radius: var(--border-radius); } .input { @@ -10,3 +12,11 @@ gap: 10px; cursor: pointer; } + +.popup { + border: 1px solid var(--base400); + background: var(--base50); + border-radius: var(--border-radius); + padding: 20px; + margin-top: 5px; +} diff --git a/components/pages/reports/PopupForm.js b/components/pages/reports/PopupForm.js index 0f0ead36..0e825a26 100644 --- a/components/pages/reports/PopupForm.js +++ b/components/pages/reports/PopupForm.js @@ -1,29 +1,11 @@ -import { createPortal } from 'react-dom'; -import { useDocumentClick, useKeyDown } from 'react-basics'; import classNames from 'classnames'; import styles from './PopupForm.module.css'; -export function PopupForm({ element, className, children, onClose }) { - const { right, top } = element.getBoundingClientRect(); - const style = { position: 'absolute', left: right, top }; - - useKeyDown('Escape', onClose); - - useDocumentClick(e => { - if (e.target !== element && !element?.parentElement?.contains(e.target)) { - onClose(); - } - }); - - const handleClick = e => { - e.stopPropagation(); - }; - - return createPortal( -
+export function PopupForm({ className, style, children }) { + return ( +
{children} -
, - document.body, +
); } diff --git a/components/pages/reports/PopupForm.module.css b/components/pages/reports/PopupForm.module.css index 4daf199a..94d98b38 100644 --- a/components/pages/reports/PopupForm.module.css +++ b/components/pages/reports/PopupForm.module.css @@ -3,8 +3,8 @@ background: var(--base50); min-width: 300px; padding: 20px; - margin-left: 30px; border: 1px solid var(--base400); border-radius: var(--border-radius); box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.1); + z-index: 1000; } diff --git a/components/pages/reports/insights/InsightsParameters.js b/components/pages/reports/insights/InsightsParameters.js index 6de4b838..9f3571e6 100644 --- a/components/pages/reports/insights/InsightsParameters.js +++ b/components/pages/reports/insights/InsightsParameters.js @@ -80,10 +80,10 @@ export function InsightsParameters() { - - {(close, element) => { + + {close => { return ( - + {id === 'fields' && ( }> handleRemove(id, index)}> - {({ name, filter, value, label }) => { + {({ name, filter, value }) => { return (
{id === 'fields' && ( diff --git a/components/pages/reports/insights/InsightsParameters.module.css b/components/pages/reports/insights/InsightsParameters.module.css index 06b62414..c84f8a9e 100644 --- a/components/pages/reports/insights/InsightsParameters.module.css +++ b/components/pages/reports/insights/InsightsParameters.module.css @@ -10,3 +10,8 @@ .op { font-weight: bold; } + +.popup { + margin-top: -10px; + margin-left: 30px; +} diff --git a/components/pages/reports/retention/RetentionParameters.js b/components/pages/reports/retention/RetentionParameters.js index 1eee6bf2..d98608ae 100644 --- a/components/pages/reports/retention/RetentionParameters.js +++ b/components/pages/reports/retention/RetentionParameters.js @@ -31,7 +31,7 @@ export function RetentionParameters() { return (
- + diff --git a/components/pages/reports/retention/RetentionReport.js b/components/pages/reports/retention/RetentionReport.js index 63eea44c..a9aaeb3e 100644 --- a/components/pages/reports/retention/RetentionReport.js +++ b/components/pages/reports/retention/RetentionReport.js @@ -6,10 +6,16 @@ import ReportMenu from '../ReportMenu'; import ReportBody from '../ReportBody'; import Magnet from 'assets/magnet.svg'; import { REPORT_TYPES } from 'lib/constants'; +import { parseDateRange } from 'lib/date'; +import { endOfMonth, startOfMonth } from 'date-fns'; const defaultParameters = { type: REPORT_TYPES.retention, - parameters: {}, + parameters: { + dateRange: parseDateRange( + `range:${startOfMonth(new Date()).getTime()}:${endOfMonth(new Date()).getTime()}`, + ), + }, }; export default function RetentionReport({ reportId }) { diff --git a/components/pages/reports/retention/RetentionTable.js b/components/pages/reports/retention/RetentionTable.js index f7d8c4bb..df0b0f99 100644 --- a/components/pages/reports/retention/RetentionTable.js +++ b/components/pages/reports/retention/RetentionTable.js @@ -1,5 +1,4 @@ import { useContext } from 'react'; -import { GridTable, GridColumn } from 'react-basics'; import classNames from 'classnames'; import { ReportContext } from '../Report'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; @@ -16,14 +15,26 @@ export function RetentionTable() { return ; } - const rows = data.reduce((arr, { date, visitors }) => { - if (!arr.find(a => a.date === date)) { - return arr.concat({ date, visitors }); + const days = [1, 2, 3, 4, 5, 6, 7, 14, 21, 28]; + + const rows = data.reduce((arr, row) => { + const { date, visitors, day } = row; + if (day === 0) { + return arr.concat({ + date, + visitors, + records: days + .reduce((arr, day) => { + arr[day] = data.find(x => x.date === date && x.day === day); + return arr; + }, []) + .filter(n => n), + }); } return arr; }, []); - const days = [1, 2, 3, 4, 5, 6, 7, 14, 21, 30]; + const totalDays = rows.length; return ( <> @@ -37,15 +48,22 @@ export function RetentionTable() {
))} - {rows.map(({ date, visitors }, i) => { + {rows.map(({ date, visitors, records }, rowIndex) => { return ( -
+
{formatDate(`${date} 00:00:00`, 'PP')}
{visitors}
- {days.map((n, day) => { + {days.map(day => { + if (totalDays - rowIndex < day) { + return null; + } + const percentage = records[day]?.percentage; return ( -
- {data.find(row => row.date === date && row.day === day)?.percentage.toFixed(2)} +
+ {percentage ? `${percentage.toFixed(2)}%` : ''}
); })} @@ -53,31 +71,8 @@ export function RetentionTable() { ); })}
- ); } -function DataTable({ data }) { - return ( - - - {row => row.date} - - - {row => row.day} - - - {row => row.visitors} - - - {row => row.returnVisitors} - - - {row => row.percentage} - - - ); -} - export default RetentionTable; diff --git a/components/pages/reports/retention/RetentionTable.module.css b/components/pages/reports/retention/RetentionTable.module.css index 79cbbc5f..bfe3ac1c 100644 --- a/components/pages/reports/retention/RetentionTable.module.css +++ b/components/pages/reports/retention/RetentionTable.module.css @@ -20,7 +20,7 @@ justify-content: center; width: 60px; height: 60px; - background: var(--blue100); + background: var(--blue200); border-radius: var(--border-radius); } @@ -46,3 +46,7 @@ font-size: var(--font-size-sm); font-weight: 400; } + +.empty { + background: var(--blue100); +} diff --git a/package.json b/package.json index 46ad4d2d..9d2de093 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", "react": "^18.2.0", - "react-basics": "^0.94.0", + "react-basics": "^0.96.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.4", diff --git a/yarn.lock b/yarn.lock index e67cc413..350e483f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7557,10 +7557,10 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-basics@^0.94.0: - version "0.94.0" - resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.94.0.tgz#c15698148b959f40c6b451088f36f5735eb82815" - integrity sha512-OlUHWrGRctRGEm+yL9iWSC9HRnxZhlm3enP2iCKytVmt7LvaPtsK4RtZ27qp4irNvuzg79aqF+h5IFnG+Vi7WA== +react-basics@^0.96.0: + version "0.96.0" + resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.96.0.tgz#e5e72201abdccdda94b952ef605163ca11772d8f" + integrity sha512-WNAxP+0xBtUNgEXrL8aW6UQMmD6WoX9My0VW6uq+Q262DOPTU3zPtWl+9vvES4pF3tPJCFvmFAlK/Alw9+XKVQ== dependencies: classnames "^2.3.1" date-fns "^2.29.3"