mirror of
https://github.com/kremalicious/umami.git
synced 2024-12-18 15:23:38 +01:00
commit
886bd7679e
@ -16,14 +16,17 @@ import BrowsersTable from './metrics/BrowsersTable';
|
|||||||
import OSTable from './metrics/OSTable';
|
import OSTable from './metrics/OSTable';
|
||||||
import DevicesTable from './metrics/DevicesTable';
|
import DevicesTable from './metrics/DevicesTable';
|
||||||
import CountriesTable from './metrics/CountriesTable';
|
import CountriesTable from './metrics/CountriesTable';
|
||||||
|
import EventsTable from './metrics/EventsTable';
|
||||||
|
import EventsChart from './metrics/EventsChart';
|
||||||
|
|
||||||
export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' }) {
|
export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' }) {
|
||||||
const [data, setData] = useState();
|
const [data, setData] = useState();
|
||||||
const [chartLoaded, setChartLoaded] = useState(false);
|
const [chartLoaded, setChartLoaded] = useState(false);
|
||||||
const [countryData, setCountryData] = useState();
|
const [countryData, setCountryData] = useState();
|
||||||
|
const [eventsData, setEventsData] = useState();
|
||||||
const [dateRange, setDateRange] = useState(getDateRange(defaultDateRange));
|
const [dateRange, setDateRange] = useState(getDateRange(defaultDateRange));
|
||||||
const [expand, setExpand] = useState();
|
const [expand, setExpand] = useState();
|
||||||
const { startDate, endDate } = dateRange;
|
const { startDate, endDate, unit } = dateRange;
|
||||||
|
|
||||||
const BackButton = () => (
|
const BackButton = () => (
|
||||||
<Button
|
<Button
|
||||||
@ -50,12 +53,18 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
|
|||||||
value: 'country',
|
value: 'country',
|
||||||
component: props => <CountriesTable {...props} onDataLoad={data => setCountryData(data)} />,
|
component: props => <CountriesTable {...props} onDataLoad={data => setCountryData(data)} />,
|
||||||
},
|
},
|
||||||
|
{ label: 'Events', value: 'event', component: EventsTable },
|
||||||
];
|
];
|
||||||
|
|
||||||
const tableProps = {
|
const dataProps = {
|
||||||
websiteId,
|
websiteId,
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
|
unit,
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableProps = {
|
||||||
|
...dataProps,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
onExpand: handleExpand,
|
onExpand: handleExpand,
|
||||||
websiteDomain: data?.domain,
|
websiteDomain: data?.domain,
|
||||||
@ -63,6 +72,10 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
|
|||||||
|
|
||||||
const DetailsComponent = expand?.component;
|
const DetailsComponent = expand?.component;
|
||||||
|
|
||||||
|
function getSelectedMenuOption(value) {
|
||||||
|
return menuOptions.find(e => e.value === value);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
setData(await get(`/api/website/${websiteId}`));
|
setData(await get(`/api/website/${websiteId}`));
|
||||||
}
|
}
|
||||||
@ -76,11 +89,11 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleExpand(value) {
|
function handleExpand(value) {
|
||||||
setExpand(menuOptions.find(e => e.value === value));
|
setExpand(getSelectedMenuOption(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMenuSelect(value) {
|
function handleMenuSelect(value) {
|
||||||
setExpand(menuOptions.find(e => e.value === value));
|
setExpand(getSelectedMenuOption(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -132,7 +145,17 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
|
|||||||
<WorldMap data={countryData} />
|
<WorldMap data={countryData} />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 col-md-12 col-lg-4">
|
<div className="col-12 col-md-12 col-lg-4">
|
||||||
<CountriesTable {...tableProps} onDataLoad={data => setCountryData(data)} />
|
<CountriesTable {...tableProps} onDataLoad={setCountryData} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={classNames(styles.row, 'row', { [styles.hidden]: !eventsData?.length > 0 })}
|
||||||
|
>
|
||||||
|
<div className="col-12 col-md-12 col-lg-4">
|
||||||
|
<EventsTable {...tableProps} onDataLoad={setEventsData} />
|
||||||
|
</div>
|
||||||
|
<div className="col-12 col-md-12 col-lg-8 pt-5 pb-5">
|
||||||
|
<EventsChart {...dataProps} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -42,6 +42,10 @@
|
|||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 992px) {
|
@media only screen and (max-width: 992px) {
|
||||||
.row {
|
.row {
|
||||||
border: 0;
|
border: 0;
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid var(--gray400);
|
border: 1px solid var(--gray500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.group .button {
|
.group .button {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: var(--gray50);
|
background: var(--gray50);
|
||||||
border-left: 1px solid var(--gray400);
|
border-left: 1px solid var(--gray500);
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,6 +16,10 @@
|
|||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.group .button:hover {
|
||||||
|
background: var(--gray100);
|
||||||
|
}
|
||||||
|
|
||||||
.group .button + .button {
|
.group .button + .button {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
13
components/common/Loading.js
Normal file
13
components/common/Loading.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import styles from './Loading.module.css';
|
||||||
|
|
||||||
|
export default function Loading({ className }) {
|
||||||
|
return (
|
||||||
|
<div className={classNames(styles.loading, className)}>
|
||||||
|
<div />
|
||||||
|
<div />
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
41
components/common/Loading.module.css
Normal file
41
components/common/Loading.module.css
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
@keyframes blink {
|
||||||
|
0% {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading div {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 100%;
|
||||||
|
background: var(--gray400);
|
||||||
|
animation: blink 1.4s infinite;
|
||||||
|
animation-fill-mode: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading div + div {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading div:nth-child(2) {
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading div:nth-child(3) {
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
}
|
@ -10,6 +10,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container .content {
|
.container .content {
|
||||||
|
position: relative;
|
||||||
border-left: 1px solid var(--gray300);
|
border-left: 1px solid var(--gray300);
|
||||||
padding-left: 30px;
|
padding-left: 30px;
|
||||||
}
|
}
|
||||||
|
168
components/metrics/BarChart.js
Normal file
168
components/metrics/BarChart.js
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
|
import ReactTooltip from 'react-tooltip';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import ChartJS from 'chart.js';
|
||||||
|
import styles from './BarChart.module.css';
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
|
||||||
|
export default function BarChart({
|
||||||
|
chartId,
|
||||||
|
datasets,
|
||||||
|
unit,
|
||||||
|
records,
|
||||||
|
height = 400,
|
||||||
|
animationDuration = 300,
|
||||||
|
className,
|
||||||
|
stacked = false,
|
||||||
|
onCreate = () => {},
|
||||||
|
onUpdate = () => {},
|
||||||
|
}) {
|
||||||
|
const canvas = useRef();
|
||||||
|
const chart = useRef();
|
||||||
|
const [tooltip, setTooltip] = useState({});
|
||||||
|
|
||||||
|
const renderLabel = (label, index, values) => {
|
||||||
|
const d = new Date(values[index].value);
|
||||||
|
const n = records;
|
||||||
|
|
||||||
|
switch (unit) {
|
||||||
|
case 'hour':
|
||||||
|
return format(d, 'ha');
|
||||||
|
case 'day':
|
||||||
|
if (n >= 15) {
|
||||||
|
return index % ~~(n / 15) === 0 ? format(d, 'MMM d') : '';
|
||||||
|
}
|
||||||
|
return format(d, 'EEE M/d');
|
||||||
|
case 'month':
|
||||||
|
return format(d, 'MMMM');
|
||||||
|
default:
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTooltip = model => {
|
||||||
|
const { opacity, title, body, labelColors } = model;
|
||||||
|
|
||||||
|
if (!opacity) {
|
||||||
|
setTooltip(null);
|
||||||
|
} else {
|
||||||
|
const [label, value] = body[0].lines[0].split(':');
|
||||||
|
|
||||||
|
setTooltip({
|
||||||
|
title: title[0],
|
||||||
|
value,
|
||||||
|
label,
|
||||||
|
labelColor: labelColors[0].backgroundColor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createChart = () => {
|
||||||
|
const options = {
|
||||||
|
animation: {
|
||||||
|
duration: animationDuration,
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
enabled: false,
|
||||||
|
custom: renderTooltip,
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
animationDuration: 0,
|
||||||
|
},
|
||||||
|
responsive: true,
|
||||||
|
responsiveAnimationDuration: 0,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
xAxes: [
|
||||||
|
{
|
||||||
|
type: 'time',
|
||||||
|
distribution: 'series',
|
||||||
|
time: {
|
||||||
|
unit,
|
||||||
|
tooltipFormat: 'ddd MMMM DD YYYY',
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
callback: renderLabel,
|
||||||
|
minRotation: 0,
|
||||||
|
maxRotation: 0,
|
||||||
|
},
|
||||||
|
gridLines: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
offset: true,
|
||||||
|
stacked: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
yAxes: [
|
||||||
|
{
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: true,
|
||||||
|
},
|
||||||
|
stacked,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
onCreate(options);
|
||||||
|
|
||||||
|
chart.current = new ChartJS(canvas.current, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
datasets,
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateChart = () => {
|
||||||
|
const { options } = chart.current;
|
||||||
|
|
||||||
|
options.scales.xAxes[0].time.unit = unit;
|
||||||
|
options.scales.xAxes[0].ticks.callback = renderLabel;
|
||||||
|
options.animation.duration = animationDuration;
|
||||||
|
|
||||||
|
onUpdate(chart.current);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (datasets) {
|
||||||
|
if (!chart.current) {
|
||||||
|
createChart();
|
||||||
|
} else {
|
||||||
|
setTooltip(null);
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [datasets, unit, animationDuration]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
data-tip=""
|
||||||
|
data-for={`${chartId}-tooltip`}
|
||||||
|
className={classNames(styles.chart, className)}
|
||||||
|
style={{ height }}
|
||||||
|
>
|
||||||
|
<canvas ref={canvas} />
|
||||||
|
</div>
|
||||||
|
<ReactTooltip id={`${chartId}-tooltip`}>
|
||||||
|
{tooltip ? <Tooltip {...tooltip} /> : null}
|
||||||
|
</ReactTooltip>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Tooltip = ({ title, value, label, labelColor }) => (
|
||||||
|
<div className={styles.tooltip}>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<div className={styles.title}>{title}</div>
|
||||||
|
<div className={styles.metric}>
|
||||||
|
<div className={styles.dot}>
|
||||||
|
<div className={styles.color} style={{ backgroundColor: labelColor }} />
|
||||||
|
</div>
|
||||||
|
{value} {label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
96
components/metrics/EventsChart.js
Normal file
96
components/metrics/EventsChart.js
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import tinycolor from 'tinycolor2';
|
||||||
|
import BarChart from './BarChart';
|
||||||
|
import { get } from 'lib/web';
|
||||||
|
import { getTimezone, getDateArray, getDateLength } from 'lib/date';
|
||||||
|
import styles from './BarChart.module.css';
|
||||||
|
|
||||||
|
const COLORS = [
|
||||||
|
'#2680eb',
|
||||||
|
'#9256d9',
|
||||||
|
'#44b556',
|
||||||
|
'#e68619',
|
||||||
|
'#e34850',
|
||||||
|
'#1b959a',
|
||||||
|
'#d83790',
|
||||||
|
'#85d044',
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function EventsChart({ websiteId, startDate, endDate, unit }) {
|
||||||
|
const [data, setData] = useState();
|
||||||
|
const datasets = useMemo(() => {
|
||||||
|
if (!data) return [];
|
||||||
|
|
||||||
|
return Object.keys(data).map((key, index) => {
|
||||||
|
const color = tinycolor(COLORS[index]);
|
||||||
|
return {
|
||||||
|
label: key,
|
||||||
|
data: data[key],
|
||||||
|
lineTension: 0,
|
||||||
|
backgroundColor: color.setAlpha(0.4).toRgbString(),
|
||||||
|
borderColor: color.setAlpha(0.5).toRgbString(),
|
||||||
|
borderWidth: 1,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
const data = await get(`/api/website/${websiteId}/events`, {
|
||||||
|
start_at: +startDate,
|
||||||
|
end_at: +endDate,
|
||||||
|
unit,
|
||||||
|
tz: getTimezone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const map = data.reduce((obj, { x, t, y }) => {
|
||||||
|
if (!obj[x]) {
|
||||||
|
obj[x] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
obj[x].push({ t, y });
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
Object.keys(map).forEach(key => {
|
||||||
|
map[key] = getDateArray(map[key], startDate, endDate, unit);
|
||||||
|
});
|
||||||
|
|
||||||
|
setData(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCreate(options) {
|
||||||
|
const legend = {
|
||||||
|
position: 'bottom',
|
||||||
|
};
|
||||||
|
|
||||||
|
options.legend = legend;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUpdate(chart) {
|
||||||
|
chart.data.datasets = datasets;
|
||||||
|
|
||||||
|
chart.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadData();
|
||||||
|
}, [websiteId, startDate, endDate]);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BarChart
|
||||||
|
chartId={`events-${websiteId}`}
|
||||||
|
datasets={datasets}
|
||||||
|
unit={unit}
|
||||||
|
records={getDateLength(startDate, endDate, unit)}
|
||||||
|
onCreate={handleCreate}
|
||||||
|
onUpdate={handleUpdate}
|
||||||
|
stacked
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
3
components/metrics/EventsChart.module.css
Normal file
3
components/metrics/EventsChart.module.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.chart {
|
||||||
|
display: flex;
|
||||||
|
}
|
37
components/metrics/EventsTable.js
Normal file
37
components/metrics/EventsTable.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import MetricsTable from './MetricsTable';
|
||||||
|
import styles from './EventsTable.module.css';
|
||||||
|
|
||||||
|
export default function EventsTable({
|
||||||
|
websiteId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
limit,
|
||||||
|
onExpand,
|
||||||
|
onDataLoad,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<MetricsTable
|
||||||
|
title="Events"
|
||||||
|
type="event"
|
||||||
|
metric="Actions"
|
||||||
|
websiteId={websiteId}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
limit={limit}
|
||||||
|
renderLabel={({ x }) => <Label value={x} />}
|
||||||
|
onExpand={onExpand}
|
||||||
|
onDataLoad={onDataLoad}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Label = ({ value }) => {
|
||||||
|
const [event, label] = value.split(':');
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className={styles.type}>{event}</span>
|
||||||
|
{label}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
7
components/metrics/EventsTable.module.css
Normal file
7
components/metrics/EventsTable.module.css
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.type {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
padding: 2px 4px;
|
||||||
|
border: 1px solid var(--gray300);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
@ -35,7 +35,7 @@ export default function MetricsBar({ websiteId, startDate, endDate, className })
|
|||||||
<MetricCard label="Visitors" value={uniques} format={formatFunc} />
|
<MetricCard label="Visitors" value={uniques} format={formatFunc} />
|
||||||
<MetricCard
|
<MetricCard
|
||||||
label="Bounce rate"
|
label="Bounce rate"
|
||||||
value={uniques ? (bounces / uniques) * 100 : 0}
|
value={pageviews ? (bounces / pageviews) * 100 : 0}
|
||||||
format={n => Number(n).toFixed(0) + '%'}
|
format={n => Number(n).toFixed(0) + '%'}
|
||||||
/>
|
/>
|
||||||
<MetricCard
|
<MetricCard
|
||||||
|
@ -8,6 +8,7 @@ import { get } from 'lib/web';
|
|||||||
import { percentFilter } from 'lib/filters';
|
import { percentFilter } from 'lib/filters';
|
||||||
import { formatNumber, formatLongNumber } from 'lib/format';
|
import { formatNumber, formatLongNumber } from 'lib/format';
|
||||||
import styles from './MetricsTable.module.css';
|
import styles from './MetricsTable.module.css';
|
||||||
|
import Loading from '../common/Loading';
|
||||||
|
|
||||||
export default function MetricsTable({
|
export default function MetricsTable({
|
||||||
title,
|
title,
|
||||||
@ -21,9 +22,9 @@ export default function MetricsTable({
|
|||||||
filterOptions,
|
filterOptions,
|
||||||
limit,
|
limit,
|
||||||
headerComponent,
|
headerComponent,
|
||||||
|
renderLabel,
|
||||||
onDataLoad = () => {},
|
onDataLoad = () => {},
|
||||||
onExpand = () => {},
|
onExpand = () => {},
|
||||||
labelRenderer = e => e,
|
|
||||||
}) {
|
}) {
|
||||||
const [data, setData] = useState();
|
const [data, setData] = useState();
|
||||||
const [format, setFormat] = useState(true);
|
const [format, setFormat] = useState(true);
|
||||||
@ -43,37 +44,34 @@ export default function MetricsTable({
|
|||||||
|
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
const data = await get(`/api/website/${websiteId}/rankings`, {
|
const data = await get(`/api/website/${websiteId}/rankings`, {
|
||||||
|
type,
|
||||||
start_at: +startDate,
|
start_at: +startDate,
|
||||||
end_at: +endDate,
|
end_at: +endDate,
|
||||||
type,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setData(data);
|
setData(data);
|
||||||
onDataLoad(data);
|
onDataLoad(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSetFormat() {
|
const handleSetFormat = () => setFormat(state => !state);
|
||||||
setFormat(state => !state);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRow(x, y, z) {
|
const getRow = row => {
|
||||||
|
const { x: label, y: value, z: percent } = row;
|
||||||
return (
|
return (
|
||||||
<AnimatedRow
|
<AnimatedRow
|
||||||
key={x}
|
key={label}
|
||||||
label={x}
|
label={renderLabel ? renderLabel(row) : label}
|
||||||
value={y}
|
value={value}
|
||||||
percent={z}
|
percent={percent}
|
||||||
animate={shouldAnimate}
|
animate={shouldAnimate}
|
||||||
format={formatFunc}
|
format={formatFunc}
|
||||||
onClick={handleSetFormat}
|
onClick={handleSetFormat}
|
||||||
labelRenderer={labelRenderer}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const Row = ({ index, style }) => {
|
const Row = ({ index, style }) => {
|
||||||
const { x, y, z } = rankings[index];
|
return <div style={style}>{getRow(rankings[index])}</div>;
|
||||||
return <div style={style}>{getRow(x, y, z)}</div>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -82,40 +80,42 @@ export default function MetricsTable({
|
|||||||
}
|
}
|
||||||
}, [websiteId, startDate, endDate, type]);
|
}, [websiteId, startDate, endDate, type]);
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles.container, className)}>
|
<div className={classNames(styles.container, className)}>
|
||||||
<div className={styles.header}>
|
{data ? (
|
||||||
<div className={styles.title}>{title}</div>
|
<>
|
||||||
{headerComponent}
|
<div className={styles.header}>
|
||||||
<div className={styles.metric} onClick={handleSetFormat}>
|
<div className={styles.title}>{title}</div>
|
||||||
{metric}
|
{headerComponent}
|
||||||
</div>
|
<div className={styles.metric} onClick={handleSetFormat}>
|
||||||
</div>
|
{metric}
|
||||||
<div className={styles.body}>
|
</div>
|
||||||
{limit ? (
|
</div>
|
||||||
rankings.map(({ x, y, z }) => getRow(x, y, z))
|
<div className={styles.body}>
|
||||||
) : (
|
{limit
|
||||||
<FixedSizeList height={600} itemCount={rankings.length} itemSize={30}>
|
? rankings.map(row => getRow(row))
|
||||||
{Row}
|
: data?.length > 0 && (
|
||||||
</FixedSizeList>
|
<FixedSizeList height={500} itemCount={rankings.length} itemSize={30}>
|
||||||
)}
|
{Row}
|
||||||
</div>
|
</FixedSizeList>
|
||||||
<div className={styles.footer}>
|
)}
|
||||||
{limit && data.length > limit && (
|
</div>
|
||||||
<Button icon={<Arrow />} size="xsmall" onClick={() => onExpand(type)}>
|
<div className={styles.footer}>
|
||||||
<div>More</div>
|
{limit && data.length > limit && (
|
||||||
</Button>
|
<Button icon={<Arrow />} size="xsmall" onClick={() => onExpand(type)}>
|
||||||
)}
|
<div>More</div>
|
||||||
</div>
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Loading />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const AnimatedRow = ({ label, value = 0, percent, animate, format, onClick, labelRenderer }) => {
|
const AnimatedRow = ({ label, value = 0, percent, animate, format, onClick }) => {
|
||||||
const props = useSpring({
|
const props = useSpring({
|
||||||
width: percent,
|
width: percent,
|
||||||
y: value,
|
y: value,
|
||||||
@ -125,7 +125,7 @@ const AnimatedRow = ({ label, value = 0, percent, animate, format, onClick, labe
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.row}>
|
<div className={styles.row}>
|
||||||
<div className={styles.label}>{labelRenderer(decodeURI(label))}</div>
|
<div className={styles.label}>{label}</div>
|
||||||
<div className={styles.value} onClick={onClick}>
|
<div className={styles.value} onClick={onClick}>
|
||||||
<animated.div className={styles.value}>{props.y?.interpolate(format)}</animated.div>
|
<animated.div className={styles.value}>{props.y?.interpolate(format)}</animated.div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,6 +25,7 @@ export default function PagesTable({
|
|||||||
limit={limit}
|
limit={limit}
|
||||||
dataFilter={urlFilter}
|
dataFilter={urlFilter}
|
||||||
filterOptions={{ domain: websiteDomain, raw: filter === 'Raw' }}
|
filterOptions={{ domain: websiteDomain, raw: filter === 'Raw' }}
|
||||||
|
renderLabel={({ x }) => decodeURI(x)}
|
||||||
onExpand={onExpand}
|
onExpand={onExpand}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1,67 +1,30 @@
|
|||||||
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
import React from 'react';
|
||||||
import ReactTooltip from 'react-tooltip';
|
import CheckVisible from 'components/helpers/CheckVisible';
|
||||||
import classNames from 'classnames';
|
import BarChart from './BarChart';
|
||||||
import ChartJS from 'chart.js';
|
|
||||||
import { format } from 'date-fns';
|
|
||||||
import styles from './PageviewsChart.module.css';
|
|
||||||
|
|
||||||
export default function PageviewsChart({
|
export default function PageviewsChart({ websiteId, data, unit, className }) {
|
||||||
websiteId,
|
const handleUpdate = chart => {
|
||||||
data,
|
const {
|
||||||
unit,
|
data: { datasets },
|
||||||
animationDuration = 300,
|
} = chart;
|
||||||
className,
|
|
||||||
children,
|
|
||||||
}) {
|
|
||||||
const canvas = useRef();
|
|
||||||
const chart = useRef();
|
|
||||||
const [tooltip, setTooltip] = useState({});
|
|
||||||
|
|
||||||
const renderLabel = useCallback(
|
datasets[0].data = data.uniques;
|
||||||
(label, index, values) => {
|
datasets[1].data = data.pageviews;
|
||||||
const d = new Date(values[index].value);
|
|
||||||
const n = data.pageviews.length;
|
|
||||||
|
|
||||||
switch (unit) {
|
chart.update();
|
||||||
case 'day':
|
|
||||||
if (n >= 15) {
|
|
||||||
return index % ~~(n / 15) === 0 ? format(d, 'MMM d') : '';
|
|
||||||
}
|
|
||||||
return format(d, 'EEE M/d');
|
|
||||||
case 'month':
|
|
||||||
return format(d, 'MMMM');
|
|
||||||
default:
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[unit, data],
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderTooltip = model => {
|
|
||||||
const { opacity, title, body, labelColors } = model;
|
|
||||||
|
|
||||||
if (!opacity) {
|
|
||||||
setTooltip(null);
|
|
||||||
} else {
|
|
||||||
const [label, value] = body[0].lines[0].split(':');
|
|
||||||
|
|
||||||
setTooltip({
|
|
||||||
title: title[0],
|
|
||||||
value,
|
|
||||||
label,
|
|
||||||
labelColor: labelColors[0].backgroundColor,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function draw() {
|
if (!data) {
|
||||||
if (!canvas.current) return;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!chart.current) {
|
return (
|
||||||
chart.current = new ChartJS(canvas.current, {
|
<CheckVisible>
|
||||||
type: 'bar',
|
{visible => (
|
||||||
data: {
|
<BarChart
|
||||||
datasets: [
|
className={className}
|
||||||
|
chartId={websiteId}
|
||||||
|
datasets={[
|
||||||
{
|
{
|
||||||
label: 'unique visitors',
|
label: 'unique visitors',
|
||||||
data: data.uniques,
|
data: data.uniques,
|
||||||
@ -78,97 +41,13 @@ export default function PageviewsChart({
|
|||||||
borderColor: 'rgb(13, 102, 208, 0.2)',
|
borderColor: 'rgb(13, 102, 208, 0.2)',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
},
|
},
|
||||||
],
|
]}
|
||||||
},
|
unit={unit}
|
||||||
options: {
|
records={data.pageviews.length}
|
||||||
animation: {
|
animationDuration={visible ? 300 : 0}
|
||||||
duration: animationDuration,
|
onUpdate={handleUpdate}
|
||||||
},
|
/>
|
||||||
tooltips: {
|
)}
|
||||||
enabled: false,
|
</CheckVisible>
|
||||||
custom: renderTooltip,
|
|
||||||
},
|
|
||||||
hover: {
|
|
||||||
animationDuration: 0,
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
xAxes: [
|
|
||||||
{
|
|
||||||
type: 'time',
|
|
||||||
distribution: 'series',
|
|
||||||
time: {
|
|
||||||
unit,
|
|
||||||
tooltipFormat: 'ddd MMMM DD YYYY',
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
callback: renderLabel,
|
|
||||||
maxRotation: 0,
|
|
||||||
},
|
|
||||||
gridLines: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
offset: true,
|
|
||||||
stacked: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
yAxes: [
|
|
||||||
{
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const {
|
|
||||||
data: { datasets },
|
|
||||||
options,
|
|
||||||
} = chart.current;
|
|
||||||
|
|
||||||
datasets[0].data = data.uniques;
|
|
||||||
datasets[1].data = data.pageviews;
|
|
||||||
options.scales.xAxes[0].time.unit = unit;
|
|
||||||
options.scales.xAxes[0].ticks.callback = renderLabel;
|
|
||||||
options.animation.duration = animationDuration;
|
|
||||||
|
|
||||||
chart.current.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data) {
|
|
||||||
draw();
|
|
||||||
setTooltip(null);
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-tip=""
|
|
||||||
data-for={`${websiteId}-tooltip`}
|
|
||||||
className={classNames(styles.chart, className)}
|
|
||||||
>
|
|
||||||
<canvas ref={canvas} width={960} height={400} />
|
|
||||||
<ReactTooltip id={`${websiteId}-tooltip`}>
|
|
||||||
{tooltip ? <Tooltip {...tooltip} /> : null}
|
|
||||||
</ReactTooltip>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Tooltip = ({ title, value, label, labelColor }) => (
|
|
||||||
<div className={styles.tooltip}>
|
|
||||||
<div className={styles.content}>
|
|
||||||
<div className={styles.title}>{title}</div>
|
|
||||||
<div className={styles.metric}>
|
|
||||||
<div className={styles.dot}>
|
|
||||||
<div className={styles.color} style={{ backgroundColor: labelColor }} />
|
|
||||||
</div>
|
|
||||||
{value} {label}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
@ -13,13 +13,13 @@ export default function Referrers({
|
|||||||
}) {
|
}) {
|
||||||
const [filter, setFilter] = useState('Combined');
|
const [filter, setFilter] = useState('Combined');
|
||||||
|
|
||||||
const renderLink = url => {
|
const renderLink = ({ x: url }) => {
|
||||||
return url.startsWith('http') ? (
|
return url.startsWith('http') ? (
|
||||||
<a href={url} target="_blank" rel="noreferrer">
|
<a href={url} target="_blank" rel="noreferrer">
|
||||||
{url}
|
{decodeURI(url)}
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
url
|
decodeURI(url)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ export default function Referrers({
|
|||||||
raw: filter === 'Raw',
|
raw: filter === 'Raw',
|
||||||
}}
|
}}
|
||||||
onExpand={onExpand}
|
onExpand={onExpand}
|
||||||
labelRenderer={renderLink}
|
renderLabel={renderLink}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React, { useState, useEffect, useMemo, useRef } from 'react';
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import PageviewsChart from './PageviewsChart';
|
import PageviewsChart from './PageviewsChart';
|
||||||
import CheckVisible from '../helpers/CheckVisible';
|
|
||||||
import MetricsBar from './MetricsBar';
|
import MetricsBar from './MetricsBar';
|
||||||
import QuickButtons from './QuickButtons';
|
import QuickButtons from './QuickButtons';
|
||||||
import DateFilter from '../common/DateFilter';
|
import DateFilter from '../common/DateFilter';
|
||||||
@ -74,18 +73,10 @@ export default function WebsiteChart({
|
|||||||
</StickyHeader>
|
</StickyHeader>
|
||||||
</div>
|
</div>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<CheckVisible className="col">
|
<div className="col">
|
||||||
{visible => (
|
<PageviewsChart websiteId={websiteId} data={{ pageviews, uniques }} unit={unit} />
|
||||||
<PageviewsChart
|
<QuickButtons value={value} onChange={handleDateChange} />
|
||||||
websiteId={websiteId}
|
</div>
|
||||||
data={{ pageviews, uniques }}
|
|
||||||
unit={unit}
|
|
||||||
animationDuration={visible ? 300 : 0}
|
|
||||||
>
|
|
||||||
<QuickButtons value={value} onChange={handleDateChange} />
|
|
||||||
</PageviewsChart>
|
|
||||||
)}
|
|
||||||
</CheckVisible>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
13
lib/date.js
13
lib/date.js
@ -98,6 +98,12 @@ export function getDateArray(data, startDate, endDate, unit) {
|
|||||||
|
|
||||||
function findData(t) {
|
function findData(t) {
|
||||||
const x = data.find(e => {
|
const x = data.find(e => {
|
||||||
|
console.log(
|
||||||
|
new Date(e.t),
|
||||||
|
getLocalTime(new Date(e.t)),
|
||||||
|
getLocalTime(new Date(e.t)).getTime(),
|
||||||
|
normalize(new Date(t)).getTime(),
|
||||||
|
);
|
||||||
return getLocalTime(new Date(e.t)).getTime() === normalize(new Date(t)).getTime();
|
return getLocalTime(new Date(e.t)).getTime() === normalize(new Date(t)).getTime();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -108,8 +114,13 @@ export function getDateArray(data, startDate, endDate, unit) {
|
|||||||
const t = add(startDate, i);
|
const t = add(startDate, i);
|
||||||
const y = findData(t);
|
const y = findData(t);
|
||||||
|
|
||||||
arr.push({ t, y });
|
arr.push({ ...data[i], t, y });
|
||||||
}
|
}
|
||||||
|
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getDateLength(startDate, endDate, unit) {
|
||||||
|
const [diff] = dateFuncs[unit];
|
||||||
|
return diff(endDate, startDate) + 1;
|
||||||
|
}
|
||||||
|
@ -35,5 +35,6 @@ export default prisma;
|
|||||||
export async function runQuery(query) {
|
export async function runQuery(query) {
|
||||||
return query.catch(e => {
|
return query.catch(e => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
throw e;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -49,12 +49,13 @@ export const urlFilter = (data, { domain, raw }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const refFilter = (data, { domain, domainOnly, raw }) => {
|
export const refFilter = (data, { domain, domainOnly, raw }) => {
|
||||||
|
const regex = new RegExp(domain.startsWith('http') ? domain : `http[s]?://${domain}`);
|
||||||
|
|
||||||
const isValidRef = ref => {
|
const isValidRef = ref => {
|
||||||
return ref !== '' && !ref.startsWith('/') && !ref.startsWith('#');
|
return ref !== '' && !ref.startsWith('/') && !ref.startsWith('#');
|
||||||
};
|
};
|
||||||
|
|
||||||
if (raw) {
|
if (raw) {
|
||||||
const regex = new RegExp(`http[s]?://([^.]+.)?${domain}`);
|
|
||||||
return data.filter(({ x }) => isValidRef(x) && !regex.test(x));
|
return data.filter(({ x }) => isValidRef(x) && !regex.test(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +63,7 @@ export const refFilter = (data, { domain, domainOnly, raw }) => {
|
|||||||
try {
|
try {
|
||||||
const { hostname, origin, pathname, searchParams, protocol } = new URL(url);
|
const { hostname, origin, pathname, searchParams, protocol } = new URL(url);
|
||||||
|
|
||||||
if (hostname === domain) {
|
if (hostname === domain || regex.test(url)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,5 +125,5 @@ export const countryFilter = data =>
|
|||||||
|
|
||||||
export const percentFilter = data => {
|
export const percentFilter = data => {
|
||||||
const total = data.reduce((n, { y }) => n + y, 0);
|
const total = data.reduce((n, { y }) => n + y, 0);
|
||||||
return data.map(({ x, y }) => ({ x, y, z: total ? (y / total) * 100 : 0 }));
|
return data.map(({ x, y, ...props }) => ({ x, y, z: total ? (y / total) * 100 : 0, ...props }));
|
||||||
};
|
};
|
||||||
|
@ -5,10 +5,28 @@ import { subMinutes } from 'date-fns';
|
|||||||
const POSTGRESQL = 'postgresql';
|
const POSTGRESQL = 'postgresql';
|
||||||
const MYSQL = 'mysql';
|
const MYSQL = 'mysql';
|
||||||
|
|
||||||
|
const DATE_FORMATS = {
|
||||||
|
minute: '%Y-%m-%d %H:%i:00',
|
||||||
|
hour: '%Y-%m-%d %H:00:00',
|
||||||
|
day: '%Y-%m-%d',
|
||||||
|
month: '%Y-%m-01',
|
||||||
|
year: '%Y-01-01',
|
||||||
|
};
|
||||||
|
|
||||||
export function getDatabase() {
|
export function getDatabase() {
|
||||||
return process.env.DATABASE_TYPE || process.env.DATABASE_URL.split(':')[0];
|
return process.env.DATABASE_TYPE || process.env.DATABASE_URL.split(':')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getDateQuery(field, unit, timezone) {
|
||||||
|
if (timezone) {
|
||||||
|
const tz = moment.tz(timezone).format('Z');
|
||||||
|
|
||||||
|
return `DATE_FORMAT(convert_tz(${field},'+00:00','${tz}'), '${DATE_FORMATS[unit]}')`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `DATE_FORMAT(${field}, '${DATE_FORMATS[unit]}')`;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getWebsiteById(website_id) {
|
export async function getWebsiteById(website_id) {
|
||||||
return runQuery(
|
return runQuery(
|
||||||
prisma.website.findOne({
|
prisma.website.findOne({
|
||||||
@ -236,7 +254,7 @@ export function getMetrics(website_id, start_at, end_at) {
|
|||||||
`
|
`
|
||||||
select sum(t.c) as "pageviews",
|
select sum(t.c) as "pageviews",
|
||||||
count(distinct t.session_id) as "uniques",
|
count(distinct t.session_id) as "uniques",
|
||||||
sum(case when t.c = 1 then t.c else 0 end) as "bounces",
|
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
|
||||||
sum(t.time) as "totaltime"
|
sum(t.time) as "totaltime"
|
||||||
from (
|
from (
|
||||||
select session_id,
|
select session_id,
|
||||||
@ -260,11 +278,11 @@ export function getMetrics(website_id, start_at, end_at) {
|
|||||||
`
|
`
|
||||||
select sum(t.c) as "pageviews",
|
select sum(t.c) as "pageviews",
|
||||||
count(distinct t.session_id) as "uniques",
|
count(distinct t.session_id) as "uniques",
|
||||||
sum(case when t.c = 1 then t.c else 0 end) as "bounces",
|
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
|
||||||
sum(t.time) as "totaltime"
|
sum(t.time) as "totaltime"
|
||||||
from (
|
from (
|
||||||
select session_id,
|
select session_id,
|
||||||
date_trunc('hour', created_at),
|
${getDateQuery('created_at', 'hour')},
|
||||||
count(*) c,
|
count(*) c,
|
||||||
floor(unix_timestamp(max(created_at)) - unix_timestamp(min(created_at))) as "time"
|
floor(unix_timestamp(max(created_at)) - unix_timestamp(min(created_at))) as "time"
|
||||||
from pageview
|
from pageview
|
||||||
@ -296,7 +314,7 @@ export function getPageviews(
|
|||||||
return prisma.$queryRaw(
|
return prisma.$queryRaw(
|
||||||
`
|
`
|
||||||
select date_trunc('${unit}', created_at at time zone '${timezone}') t,
|
select date_trunc('${unit}', created_at at time zone '${timezone}') t,
|
||||||
count(${count}) y
|
count(${count}) y
|
||||||
from pageview
|
from pageview
|
||||||
where website_id=$1
|
where website_id=$1
|
||||||
and created_at between $2 and $3
|
and created_at between $2 and $3
|
||||||
@ -310,11 +328,10 @@ export function getPageviews(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (db === MYSQL) {
|
if (db === MYSQL) {
|
||||||
const tz = moment.tz(timezone).format('Z');
|
|
||||||
return prisma.$queryRaw(
|
return prisma.$queryRaw(
|
||||||
`
|
`
|
||||||
select date_trunc('${unit}', convert_tz(created_at,'+00:00','${tz}')) t,
|
select ${getDateQuery('created_at', unit, timezone)} t,
|
||||||
count(${count}) y
|
count(${count}) y
|
||||||
from pageview
|
from pageview
|
||||||
where website_id=?
|
where website_id=?
|
||||||
and created_at between ? and ?
|
and created_at between ? and ?
|
||||||
@ -400,3 +417,47 @@ export function getActiveVisitors(website_id) {
|
|||||||
|
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getEvents(website_id, start_at, end_at, timezone = 'utc', unit = 'day') {
|
||||||
|
const db = getDatabase();
|
||||||
|
|
||||||
|
if (db === POSTGRESQL) {
|
||||||
|
return prisma.$queryRaw(
|
||||||
|
`
|
||||||
|
select
|
||||||
|
event_value x,
|
||||||
|
date_trunc('${unit}', created_at at time zone '${timezone}') t,
|
||||||
|
count(*) y
|
||||||
|
from event
|
||||||
|
where website_id=$1
|
||||||
|
and created_at between $2 and $3
|
||||||
|
group by 1, 2
|
||||||
|
order by 2
|
||||||
|
`,
|
||||||
|
website_id,
|
||||||
|
start_at,
|
||||||
|
end_at,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db === MYSQL) {
|
||||||
|
return prisma.$queryRaw(
|
||||||
|
`
|
||||||
|
select
|
||||||
|
event_value x,
|
||||||
|
${getDateQuery('created_at', unit, timezone)} t,
|
||||||
|
count(*) y
|
||||||
|
from event
|
||||||
|
where website_id=?
|
||||||
|
and created_at between ? and ?
|
||||||
|
group by 1, 2
|
||||||
|
order by 2
|
||||||
|
`,
|
||||||
|
website_id,
|
||||||
|
start_at,
|
||||||
|
end_at,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
12
package.json
12
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "umami",
|
"name": "umami",
|
||||||
"version": "0.16.3",
|
"version": "0.17.0",
|
||||||
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
|
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
|
||||||
"author": "Mike Cao <mike@mikecao.com>",
|
"author": "Mike Cao <mike@mikecao.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -45,7 +45,7 @@
|
|||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"cookie": "^0.4.1",
|
"cookie": "^0.4.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"date-fns": "^2.15.0",
|
"date-fns": "^2.16.0",
|
||||||
"date-fns-tz": "^1.0.10",
|
"date-fns-tz": "^1.0.10",
|
||||||
"detect-browser": "^5.1.1",
|
"detect-browser": "^5.1.1",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
@ -62,7 +62,7 @@
|
|||||||
"react-redux": "^7.2.1",
|
"react-redux": "^7.2.1",
|
||||||
"react-simple-maps": "^2.1.2",
|
"react-simple-maps": "^2.1.2",
|
||||||
"react-spring": "^8.0.27",
|
"react-spring": "^8.0.27",
|
||||||
"react-tooltip": "^4.2.8",
|
"react-tooltip": "^4.2.9",
|
||||||
"react-window": "^1.8.5",
|
"react-window": "^1.8.5",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
@ -86,14 +86,14 @@
|
|||||||
"eslint-plugin-react": "^7.20.6",
|
"eslint-plugin-react": "^7.20.6",
|
||||||
"eslint-plugin-react-hooks": "^4.1.0",
|
"eslint-plugin-react-hooks": "^4.1.0",
|
||||||
"husky": "^4.2.5",
|
"husky": "^4.2.5",
|
||||||
"lint-staged": "^10.2.9",
|
"lint-staged": "^10.2.13",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"postcss-flexbugs-fixes": "^4.2.1",
|
"postcss-flexbugs-fixes": "^4.2.1",
|
||||||
"postcss-import": "^12.0.1",
|
"postcss-import": "^12.0.1",
|
||||||
"postcss-preset-env": "^6.7.0",
|
"postcss-preset-env": "^6.7.0",
|
||||||
"prettier": "^2.0.5",
|
"prettier": "^2.1.1",
|
||||||
"prettier-eslint": "^11.0.0",
|
"prettier-eslint": "^11.0.0",
|
||||||
"rollup": "^2.26.5",
|
"rollup": "^2.26.6",
|
||||||
"rollup-plugin-hashbang": "^2.2.2",
|
"rollup-plugin-hashbang": "^2.2.2",
|
||||||
"rollup-plugin-terser": "^7.0.0",
|
"rollup-plugin-terser": "^7.0.0",
|
||||||
"stylelint": "^13.6.0",
|
"stylelint": "^13.6.0",
|
||||||
|
21
pages/api/website/[id]/events.js
Normal file
21
pages/api/website/[id]/events.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import moment from 'moment-timezone';
|
||||||
|
import { getEvents } from 'lib/queries';
|
||||||
|
import { ok, badRequest } from 'lib/response';
|
||||||
|
|
||||||
|
const unitTypes = ['month', 'hour', 'day'];
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id, start_at, end_at, unit, tz } = req.query;
|
||||||
|
|
||||||
|
if (!moment.tz.zone(tz) || !unitTypes.includes(unit)) {
|
||||||
|
return badRequest(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
const websiteId = +id;
|
||||||
|
const startDate = new Date(+start_at);
|
||||||
|
const endDate = new Date(+end_at);
|
||||||
|
|
||||||
|
const events = await getEvents(websiteId, startDate, endDate, tz, unit);
|
||||||
|
|
||||||
|
return ok(res, events);
|
||||||
|
};
|
@ -11,12 +11,13 @@ export default async (req, res) => {
|
|||||||
return badRequest(res);
|
return badRequest(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const start = new Date(+start_at);
|
const websiteId = +id;
|
||||||
const end = new Date(+end_at);
|
const startDate = new Date(+start_at);
|
||||||
|
const endDate = new Date(+end_at);
|
||||||
|
|
||||||
const [pageviews, uniques] = await Promise.all([
|
const [pageviews, uniques] = await Promise.all([
|
||||||
getPageviews(+id, start, end, tz, unit, '*'),
|
getPageviews(websiteId, startDate, endDate, tz, unit, '*'),
|
||||||
getPageviews(+id, start, end, tz, unit, 'distinct session_id'),
|
getPageviews(websiteId, startDate, endDate, tz, unit, 'distinct session_id'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return ok(res, { pageviews, uniques });
|
return ok(res, { pageviews, uniques });
|
||||||
|
@ -4,16 +4,42 @@ import { ok, badRequest } from 'lib/response';
|
|||||||
const sessionColumns = ['browser', 'os', 'device', 'country'];
|
const sessionColumns = ['browser', 'os', 'device', 'country'];
|
||||||
const pageviewColumns = ['url', 'referrer'];
|
const pageviewColumns = ['url', 'referrer'];
|
||||||
|
|
||||||
|
function getTable(type) {
|
||||||
|
if (type === 'event') {
|
||||||
|
return 'event';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sessionColumns.includes(type)) {
|
||||||
|
return 'session';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'pageview';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColumn(type) {
|
||||||
|
if (type === 'event') {
|
||||||
|
return `concat(event_type, ':', event_value)`;
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const { id, type, start_at, end_at } = req.query;
|
const { id, type, start_at, end_at } = req.query;
|
||||||
|
const websiteId = +id;
|
||||||
|
const startDate = new Date(+start_at);
|
||||||
|
const endDate = new Date(+end_at);
|
||||||
|
|
||||||
if (!sessionColumns.includes(type) && !pageviewColumns.includes(type)) {
|
if (type !== 'event' && !sessionColumns.includes(type) && !pageviewColumns.includes(type)) {
|
||||||
return badRequest(res);
|
return badRequest(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const table = sessionColumns.includes(type) ? 'session' : 'pageview';
|
const rankings = await getRankings(
|
||||||
|
websiteId,
|
||||||
const rankings = await getRankings(+id, new Date(+start_at), new Date(+end_at), type, table);
|
startDate,
|
||||||
|
endDate,
|
||||||
|
getColumn(type),
|
||||||
|
getTable(type),
|
||||||
|
);
|
||||||
|
|
||||||
return ok(res, rankings);
|
return ok(res, rankings);
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,6 @@ drop table if exists pageview;
|
|||||||
drop table if exists session;
|
drop table if exists session;
|
||||||
drop table if exists website;
|
drop table if exists website;
|
||||||
drop table if exists account;
|
drop table if exists account;
|
||||||
drop function if exists date_trunc;
|
|
||||||
|
|
||||||
create table account (
|
create table account (
|
||||||
user_id int unsigned not null auto_increment primary key,
|
user_id int unsigned not null auto_increment primary key,
|
||||||
@ -71,37 +70,11 @@ create index session_website_id_idx on session(website_id);
|
|||||||
create index pageview_created_at_idx on pageview(created_at);
|
create index pageview_created_at_idx on pageview(created_at);
|
||||||
create index pageview_website_id_idx on pageview(website_id);
|
create index pageview_website_id_idx on pageview(website_id);
|
||||||
create index pageview_session_id_idx on pageview(session_id);
|
create index pageview_session_id_idx on pageview(session_id);
|
||||||
|
create index pageview_website_id_created_at_idx on pageview(website_id, created_at);
|
||||||
|
create index pageview_website_id_session_id_created_at_idx on pageview(website_id, session_id, created_at);
|
||||||
|
|
||||||
create index event_created_at_idx on event(created_at);
|
create index event_created_at_idx on event(created_at);
|
||||||
create index event_website_id_idx on event(website_id);
|
create index event_website_id_idx on event(website_id);
|
||||||
create index event_session_id_idx on event(session_id);
|
create index event_session_id_idx on event(session_id);
|
||||||
|
|
||||||
delimiter $$
|
|
||||||
|
|
||||||
create function date_trunc(
|
|
||||||
in_granularity enum('minute', 'hour', 'day', 'month', 'year'),
|
|
||||||
in_datetime datetime(6)
|
|
||||||
)
|
|
||||||
returns datetime(6)
|
|
||||||
deterministic
|
|
||||||
begin
|
|
||||||
if (in_granularity = 'minute') then
|
|
||||||
return DATE_FORMAT(in_datetime, '%Y-%m-%d %H:%i:00.0000');
|
|
||||||
end if;
|
|
||||||
if (in_granularity = 'hour') then
|
|
||||||
return DATE_FORMAT(in_datetime, '%Y-%m-%d %H:00:00.0000');
|
|
||||||
end if;
|
|
||||||
if (in_granularity = 'day') then
|
|
||||||
return DATE_FORMAT(in_datetime, '%Y-%m-%d 00:00:00.0000');
|
|
||||||
end if;
|
|
||||||
if (in_granularity = 'month') then
|
|
||||||
return DATE_FORMAT(in_datetime, '%Y-%m-01 00:00:00.0000');
|
|
||||||
end if;
|
|
||||||
if (in_granularity = 'year') then
|
|
||||||
return DATE_FORMAT(in_datetime, '%Y-01-01 00:00:00.0000');
|
|
||||||
end if;
|
|
||||||
end;
|
|
||||||
|
|
||||||
$$
|
|
||||||
|
|
||||||
insert into account (username, password, is_admin) values ('admin', '$2b$10$BUli0c.muyCW1ErNJc3jL.vFRFtFJWrT8/GcR4A.sUdCznaXiqFXa', true);
|
insert into account (username, password, is_admin) values ('admin', '$2b$10$BUli0c.muyCW1ErNJc3jL.vFRFtFJWrT8/GcR4A.sUdCznaXiqFXa', true);
|
@ -64,6 +64,8 @@ create index session_website_id_idx on session(website_id);
|
|||||||
create index pageview_created_at_idx on pageview(created_at);
|
create index pageview_created_at_idx on pageview(created_at);
|
||||||
create index pageview_website_id_idx on pageview(website_id);
|
create index pageview_website_id_idx on pageview(website_id);
|
||||||
create index pageview_session_id_idx on pageview(session_id);
|
create index pageview_session_id_idx on pageview(session_id);
|
||||||
|
create index pageview_website_id_created_at_idx on pageview(website_id, created_at);
|
||||||
|
create index pageview_website_id_session_id_created_at_idx on pageview(website_id, session_id, created_at);
|
||||||
|
|
||||||
create index event_created_at_idx on event(created_at);
|
create index event_created_at_idx on event(created_at);
|
||||||
create index event_website_id_idx on event(website_id);
|
create index event_website_id_idx on event(website_id);
|
||||||
|
186
yarn.lock
186
yarn.lock
@ -1086,7 +1086,7 @@
|
|||||||
levenary "^1.1.1"
|
levenary "^1.1.1"
|
||||||
semver "^5.5.0"
|
semver "^5.5.0"
|
||||||
|
|
||||||
"@babel/preset-modules@0.1.3", "@babel/preset-modules@^0.1.3":
|
"@babel/preset-modules@0.1.3":
|
||||||
version "0.1.3"
|
version "0.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72"
|
resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72"
|
||||||
integrity sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==
|
integrity sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==
|
||||||
@ -1097,6 +1097,17 @@
|
|||||||
"@babel/types" "^7.4.4"
|
"@babel/types" "^7.4.4"
|
||||||
esutils "^2.0.2"
|
esutils "^2.0.2"
|
||||||
|
|
||||||
|
"@babel/preset-modules@^0.1.3":
|
||||||
|
version "0.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e"
|
||||||
|
integrity sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-plugin-utils" "^7.0.0"
|
||||||
|
"@babel/plugin-proposal-unicode-property-regex" "^7.4.4"
|
||||||
|
"@babel/plugin-transform-dotall-regex" "^7.4.4"
|
||||||
|
"@babel/types" "^7.4.4"
|
||||||
|
esutils "^2.0.2"
|
||||||
|
|
||||||
"@babel/preset-react@7.9.4":
|
"@babel/preset-react@7.9.4":
|
||||||
version "7.9.4"
|
version "7.9.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.9.4.tgz#c6c97693ac65b6b9c0b4f25b948a8f665463014d"
|
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.9.4.tgz#c6c97693ac65b6b9c0b4f25b948a8f665463014d"
|
||||||
@ -1458,9 +1469,9 @@
|
|||||||
integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=
|
integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=
|
||||||
|
|
||||||
"@types/node@*":
|
"@types/node@*":
|
||||||
version "14.6.0"
|
version "14.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.0.tgz#7d4411bf5157339337d7cff864d9ff45f177b499"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.1.tgz#fdf6f6c6c73d3d8eee9c98a9a0485bc524b048d7"
|
||||||
integrity sha512-mikldZQitV94akrc4sCcSjtJfsTKt4p+e/s0AGscVA6XArQ9kFclP+ZiYUMnq987rc6QlYxXv/EivqlfSLxpKA==
|
integrity sha512-HnYlg/BRF8uC1FyKRFZwRaCPTPYKa+6I8QiUZFLredaGOou481cgFS4wKRFyKvQtX8xudqkSdBczJHIYSQYKrQ==
|
||||||
|
|
||||||
"@types/normalize-package-data@^2.4.0":
|
"@types/normalize-package-data@^2.4.0":
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
@ -1489,40 +1500,40 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
|
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
|
||||||
integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
|
integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
|
||||||
|
|
||||||
"@typescript-eslint/experimental-utils@3.9.1":
|
"@typescript-eslint/experimental-utils@3.10.1":
|
||||||
version "3.9.1"
|
version "3.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.9.1.tgz#b140b2dc7a7554a44f8a86fb6fe7cbfe57ca059e"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686"
|
||||||
integrity sha512-lkiZ8iBBaYoyEKhCkkw4SAeatXyBq9Ece5bZXdLe1LWBUwTszGbmbiqmQbwWA8cSYDnjWXp9eDbXpf9Sn0hLAg==
|
integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/json-schema" "^7.0.3"
|
"@types/json-schema" "^7.0.3"
|
||||||
"@typescript-eslint/types" "3.9.1"
|
"@typescript-eslint/types" "3.10.1"
|
||||||
"@typescript-eslint/typescript-estree" "3.9.1"
|
"@typescript-eslint/typescript-estree" "3.10.1"
|
||||||
eslint-scope "^5.0.0"
|
eslint-scope "^5.0.0"
|
||||||
eslint-utils "^2.0.0"
|
eslint-utils "^2.0.0"
|
||||||
|
|
||||||
"@typescript-eslint/parser@^3.0.0":
|
"@typescript-eslint/parser@^3.0.0":
|
||||||
version "3.9.1"
|
version "3.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.9.1.tgz#ab7983abaea0ae138ff5671c7c7739d8a191b181"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467"
|
||||||
integrity sha512-y5QvPFUn4Vl4qM40lI+pNWhTcOWtpZAJ8pOEQ21fTTW4xTJkRplMjMRje7LYTXqVKKX9GJhcyweMz2+W1J5bMg==
|
integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/eslint-visitor-keys" "^1.0.0"
|
"@types/eslint-visitor-keys" "^1.0.0"
|
||||||
"@typescript-eslint/experimental-utils" "3.9.1"
|
"@typescript-eslint/experimental-utils" "3.10.1"
|
||||||
"@typescript-eslint/types" "3.9.1"
|
"@typescript-eslint/types" "3.10.1"
|
||||||
"@typescript-eslint/typescript-estree" "3.9.1"
|
"@typescript-eslint/typescript-estree" "3.10.1"
|
||||||
eslint-visitor-keys "^1.1.0"
|
eslint-visitor-keys "^1.1.0"
|
||||||
|
|
||||||
"@typescript-eslint/types@3.9.1":
|
"@typescript-eslint/types@3.10.1":
|
||||||
version "3.9.1"
|
version "3.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.9.1.tgz#b2a6eaac843cf2f2777b3f2464fb1fbce5111416"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727"
|
||||||
integrity sha512-15JcTlNQE1BsYy5NBhctnEhEoctjXOjOK+Q+rk8ugC+WXU9rAcS2BYhoh6X4rOaXJEpIYDl+p7ix+A5U0BqPTw==
|
integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree@3.9.1":
|
"@typescript-eslint/typescript-estree@3.10.1":
|
||||||
version "3.9.1"
|
version "3.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.9.1.tgz#fd81cada74bc8a7f3a2345b00897acb087935779"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853"
|
||||||
integrity sha512-IqM0gfGxOmIKPhiHW/iyAEXwSVqMmR2wJ9uXHNdFpqVvPaQ3dWg302vW127sBpAiqM9SfHhyS40NKLsoMpN2KA==
|
integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types" "3.9.1"
|
"@typescript-eslint/types" "3.10.1"
|
||||||
"@typescript-eslint/visitor-keys" "3.9.1"
|
"@typescript-eslint/visitor-keys" "3.10.1"
|
||||||
debug "^4.1.1"
|
debug "^4.1.1"
|
||||||
glob "^7.1.6"
|
glob "^7.1.6"
|
||||||
is-glob "^4.0.1"
|
is-glob "^4.0.1"
|
||||||
@ -1530,10 +1541,10 @@
|
|||||||
semver "^7.3.2"
|
semver "^7.3.2"
|
||||||
tsutils "^3.17.1"
|
tsutils "^3.17.1"
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys@3.9.1":
|
"@typescript-eslint/visitor-keys@3.10.1":
|
||||||
version "3.9.1"
|
version "3.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.9.1.tgz#92af3747cdb71509199a8f7a4f00b41d636551d1"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931"
|
||||||
integrity sha512-zxdtUjeoSh+prCpogswMwVUJfEFmCOjdzK9rpNjNBfm6EyPt99x3RrJoBOGZO23FCt0WPKUCOL5mb/9D5LjdwQ==
|
integrity sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint-visitor-keys "^1.1.0"
|
eslint-visitor-keys "^1.1.0"
|
||||||
|
|
||||||
@ -2352,9 +2363,9 @@ camelcase@^6.0.0:
|
|||||||
integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==
|
integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001093, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001111:
|
caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001093, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001111:
|
||||||
version "1.0.30001117"
|
version "1.0.30001119"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001117.tgz#69a9fae5d480eaa9589f7641a83842ad396d17c4"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001119.tgz#99185d04bc00e76a86c9ff731dc5ec8e53aefca1"
|
||||||
integrity sha512-4tY0Fatzdx59kYjQs+bNxUwZB03ZEBgVmJ1UkFPz/Q8OLiUUbjct2EdpnXj0fvFTPej2EkbPIG0w8BWsjAyk1Q==
|
integrity sha512-Hpwa4obv7EGP+TjkCh/wVvbtNJewxmtg4yVJBLFnxo35vbPapBr138bUWENkb5j5L9JZJ9RXLn4OrXRG/cecPQ==
|
||||||
|
|
||||||
ccount@^1.0.0:
|
ccount@^1.0.0:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
@ -2536,7 +2547,7 @@ cli-cursor@^3.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
restore-cursor "^3.1.0"
|
restore-cursor "^3.1.0"
|
||||||
|
|
||||||
cli-truncate@2.1.0, cli-truncate@^2.1.0:
|
cli-truncate@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7"
|
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7"
|
||||||
integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==
|
integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==
|
||||||
@ -2637,10 +2648,10 @@ commander@2, commander@^2.20.0:
|
|||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||||
|
|
||||||
commander@^5.1.0:
|
commander@^6.0.0:
|
||||||
version "5.1.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-6.1.0.tgz#f8d722b78103141006b66f4c7ba1e97315ba75bc"
|
||||||
integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
|
integrity sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA==
|
||||||
|
|
||||||
common-tags@^1.4.0:
|
common-tags@^1.4.0:
|
||||||
version "1.8.0"
|
version "1.8.0"
|
||||||
@ -2765,6 +2776,17 @@ cosmiconfig@^6.0.0:
|
|||||||
path-type "^4.0.0"
|
path-type "^4.0.0"
|
||||||
yaml "^1.7.2"
|
yaml "^1.7.2"
|
||||||
|
|
||||||
|
cosmiconfig@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3"
|
||||||
|
integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==
|
||||||
|
dependencies:
|
||||||
|
"@types/parse-json" "^4.0.0"
|
||||||
|
import-fresh "^3.2.1"
|
||||||
|
parse-json "^5.0.0"
|
||||||
|
path-type "^4.0.0"
|
||||||
|
yaml "^1.10.0"
|
||||||
|
|
||||||
create-ecdh@^4.0.0:
|
create-ecdh@^4.0.0:
|
||||||
version "4.0.4"
|
version "4.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
|
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
|
||||||
@ -3092,10 +3114,10 @@ date-fns-tz@^1.0.10:
|
|||||||
resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.0.10.tgz#30fef0038f80534fddd8e133a6b8ca55ba313748"
|
resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.0.10.tgz#30fef0038f80534fddd8e133a6b8ca55ba313748"
|
||||||
integrity sha512-cHQAz0/9uDABaUNDM80Mj1FL4ODlxs1xEY4b0DQuAooO2UdNKvDkNbV8ogLnxLbv02Ru1HXFcot0pVvDRBgptg==
|
integrity sha512-cHQAz0/9uDABaUNDM80Mj1FL4ODlxs1xEY4b0DQuAooO2UdNKvDkNbV8ogLnxLbv02Ru1HXFcot0pVvDRBgptg==
|
||||||
|
|
||||||
date-fns@^2.15.0:
|
date-fns@^2.16.0:
|
||||||
version "2.15.0"
|
version "2.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.15.0.tgz#424de6b3778e4e69d3ff27046ec136af58ae5d5f"
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.0.tgz#d34f0f5f2fd498c984513042e8f7247ea86c4cb7"
|
||||||
integrity sha512-ZCPzAMJZn3rNUvvQIMlXhDr4A+Ar07eLeGsGREoWU19a3Pqf5oYa+ccd+B3F6XVtQY6HANMFdOQ8A+ipFnvJdQ==
|
integrity sha512-DWTRyfOA85sZ4IiXPHhiRIOs3fW5U6Msrp+gElXARa6EpoQTXPyHQmh7hr+ssw2nx9FtOQWnAMJKgL5vaJqILw==
|
||||||
|
|
||||||
debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
|
debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
@ -3351,9 +3373,9 @@ duplexify@^3.4.2, duplexify@^3.6.0:
|
|||||||
stream-shift "^1.0.0"
|
stream-shift "^1.0.0"
|
||||||
|
|
||||||
electron-to-chromium@^1.3.488, electron-to-chromium@^1.3.523:
|
electron-to-chromium@^1.3.488, electron-to-chromium@^1.3.523:
|
||||||
version "1.3.544"
|
version "1.3.553"
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.544.tgz#ac1f7d319f6060f3d6d122261d542ec77eb1427e"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.553.tgz#c4693d8660470a3aa830907890e446a9f3b26376"
|
||||||
integrity sha512-jx6H7M1db76Q/dI3MadZC4qwNTvpiq8tdYEJswxexrIm5bH+LKRdg+VAteMF1tJJbBLrcuogE9N3nxT3Dp1gag==
|
integrity sha512-wi/hoMuTGK6OJoLOHqmXFA9BWOQGF2nInCfk+/Owhd4VVfuenKE2LZr9TtFCmwyda2SE9hG+sRnqRCwhYgFeIg==
|
||||||
|
|
||||||
elliptic@^6.5.3:
|
elliptic@^6.5.3:
|
||||||
version "6.5.3"
|
version "6.5.3"
|
||||||
@ -3404,7 +3426,7 @@ enhanced-resolve@^4.3.0:
|
|||||||
memory-fs "^0.5.0"
|
memory-fs "^0.5.0"
|
||||||
tapable "^1.0.0"
|
tapable "^1.0.0"
|
||||||
|
|
||||||
enquirer@^2.3.5:
|
enquirer@^2.3.5, enquirer@^2.3.6:
|
||||||
version "2.3.6"
|
version "2.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
|
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
|
||||||
integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
|
integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
|
||||||
@ -3746,7 +3768,7 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
|
|||||||
md5.js "^1.3.4"
|
md5.js "^1.3.4"
|
||||||
safe-buffer "^5.1.1"
|
safe-buffer "^5.1.1"
|
||||||
|
|
||||||
execa@^4.0.1:
|
execa@^4.0.3:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2"
|
resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2"
|
||||||
integrity sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==
|
integrity sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==
|
||||||
@ -4435,11 +4457,11 @@ ignore@^5.1.4, ignore@^5.1.8:
|
|||||||
integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
|
integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
|
||||||
|
|
||||||
immer@^7.0.3:
|
immer@^7.0.3:
|
||||||
version "7.0.7"
|
version "7.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.7.tgz#9dfe713d49bf871cc59aedfce59b1992fa37a977"
|
resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.8.tgz#41dcbc5669a76500d017bef3ad0d03ce0a1d7c1e"
|
||||||
integrity sha512-Q8yYwVADJXrNfp1ZUAh4XDHkcoE3wpdpb4mC5abDSajs2EbW8+cGdPyAnglMyLnm7EF6ojD2xBFX7L5i4TIytw==
|
integrity sha512-XnpIN8PXBBaOD43U8Z17qg6RQiKQYGDGGCIbz1ixmLGwBkSWwmrmx5X7d+hTtXDM8ur7m5OdLE0PiO+y5RB3pw==
|
||||||
|
|
||||||
import-fresh@^3.0.0, import-fresh@^3.1.0:
|
import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1:
|
||||||
version "3.2.1"
|
version "3.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66"
|
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66"
|
||||||
integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==
|
integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==
|
||||||
@ -4999,20 +5021,20 @@ lines-and-columns@^1.1.6:
|
|||||||
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
|
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
|
||||||
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
|
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
|
||||||
|
|
||||||
lint-staged@^10.2.9:
|
lint-staged@^10.2.13:
|
||||||
version "10.2.11"
|
version "10.2.13"
|
||||||
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.11.tgz#713c80877f2dc8b609b05bc59020234e766c9720"
|
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.13.tgz#b9c504683470edfc464b7d3fe3845a5a1efcd814"
|
||||||
integrity sha512-LRRrSogzbixYaZItE2APaS4l2eJMjjf5MbclRZpLJtcQJShcvUzKXsNeZgsLIZ0H0+fg2tL4B59fU9wHIHtFIA==
|
integrity sha512-conwlukNV6aL9SiMWjFtDp5exeDnTMekdNPDZsKGnpfQuHcO0E3L3Bbf58lcR+M7vk6LpCilxDAVks/DDVBYlA==
|
||||||
dependencies:
|
dependencies:
|
||||||
chalk "^4.0.0"
|
chalk "^4.1.0"
|
||||||
cli-truncate "2.1.0"
|
cli-truncate "^2.1.0"
|
||||||
commander "^5.1.0"
|
commander "^6.0.0"
|
||||||
cosmiconfig "^6.0.0"
|
cosmiconfig "^7.0.0"
|
||||||
debug "^4.1.1"
|
debug "^4.1.1"
|
||||||
dedent "^0.7.0"
|
dedent "^0.7.0"
|
||||||
enquirer "^2.3.5"
|
enquirer "^2.3.6"
|
||||||
execa "^4.0.1"
|
execa "^4.0.3"
|
||||||
listr2 "^2.1.0"
|
listr2 "^2.6.0"
|
||||||
log-symbols "^4.0.0"
|
log-symbols "^4.0.0"
|
||||||
micromatch "^4.0.2"
|
micromatch "^4.0.2"
|
||||||
normalize-path "^3.0.0"
|
normalize-path "^3.0.0"
|
||||||
@ -5020,10 +5042,10 @@ lint-staged@^10.2.9:
|
|||||||
string-argv "0.3.1"
|
string-argv "0.3.1"
|
||||||
stringify-object "^3.3.0"
|
stringify-object "^3.3.0"
|
||||||
|
|
||||||
listr2@^2.1.0:
|
listr2@^2.6.0:
|
||||||
version "2.6.0"
|
version "2.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.6.0.tgz#788a3d202978a1b8582062952cbc49272c8e206a"
|
resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.6.1.tgz#fbbabd8eea723924df7530042c1990b346e81706"
|
||||||
integrity sha512-nwmqTJYQQ+AsKb4fCXH/6/UmLCEDL1jkRAdSn9M6cEUzoRGrs33YD/3N86gAZQnGZ6hxV18XSdlBcJ1GTmetJA==
|
integrity sha512-1aPX9GkS+W0aHfPUDedJqeqj0DOe1605NaNoqdwEYw/UF2UbZgCIIMpXXZALeG/8xzwMBztguzQEubU5Xw1Qbw==
|
||||||
dependencies:
|
dependencies:
|
||||||
chalk "^4.1.0"
|
chalk "^4.1.0"
|
||||||
cli-truncate "^2.1.0"
|
cli-truncate "^2.1.0"
|
||||||
@ -5164,9 +5186,9 @@ loglevel-colored-level-prefix@^1.0.0:
|
|||||||
loglevel "^1.4.1"
|
loglevel "^1.4.1"
|
||||||
|
|
||||||
loglevel@^1.4.1:
|
loglevel@^1.4.1:
|
||||||
version "1.6.8"
|
version "1.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171"
|
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0"
|
||||||
integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==
|
integrity sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ==
|
||||||
|
|
||||||
longest-streak@^2.0.1:
|
longest-streak@^2.0.1:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
@ -6779,10 +6801,10 @@ prettier-linter-helpers@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fast-diff "^1.1.2"
|
fast-diff "^1.1.2"
|
||||||
|
|
||||||
prettier@^2.0.0, prettier@^2.0.5:
|
prettier@^2.0.0, prettier@^2.1.1:
|
||||||
version "2.0.5"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4"
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.1.tgz#d9485dd5e499daa6cb547023b87a6cf51bee37d6"
|
||||||
integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==
|
integrity sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw==
|
||||||
|
|
||||||
pretty-format@^23.0.1:
|
pretty-format@^23.0.1:
|
||||||
version "23.6.0"
|
version "23.6.0"
|
||||||
@ -6999,10 +7021,10 @@ react-spring@^8.0.27:
|
|||||||
"@babel/runtime" "^7.3.1"
|
"@babel/runtime" "^7.3.1"
|
||||||
prop-types "^15.5.8"
|
prop-types "^15.5.8"
|
||||||
|
|
||||||
react-tooltip@^4.2.8:
|
react-tooltip@^4.2.9:
|
||||||
version "4.2.8"
|
version "4.2.9"
|
||||||
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.8.tgz#270858fee46fab73b66de316271aa94145f7446b"
|
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.9.tgz#0dd08d14191f5d0e56b51c822fa20c2d81a24272"
|
||||||
integrity sha512-pDWa0/khTAgIfldp95tHgyuYyBhWNlfaU2LF9ubAKxpoqNe15uyf+uLlnhK/Lstb6FU8E8/SL28Wp6oEO9xw3g==
|
integrity sha512-DgZyg5oxk9/orgePDLLeuDtlwwYv7CalJRahk9nNsoEJDzIO58GC6zSAet4bKTm6c01hg1z3EocP9H0nmMHTMA==
|
||||||
dependencies:
|
dependencies:
|
||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
uuid "^7.0.3"
|
uuid "^7.0.3"
|
||||||
@ -7417,10 +7439,10 @@ rollup-plugin-terser@^7.0.0:
|
|||||||
serialize-javascript "^4.0.0"
|
serialize-javascript "^4.0.0"
|
||||||
terser "^5.0.0"
|
terser "^5.0.0"
|
||||||
|
|
||||||
rollup@^2.26.5:
|
rollup@^2.26.6:
|
||||||
version "2.26.5"
|
version "2.26.6"
|
||||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.26.5.tgz#5562ec36fcba3eed65cfd630bd78e037ad0e0307"
|
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.26.6.tgz#0b460c1da224c6af12a1e948a28c513aa11f2b93"
|
||||||
integrity sha512-rCyFG3ZtQdnn9YwfuAVH0l/Om34BdO5lwCA0W6Hq+bNB21dVEBbCRxhaHOmu1G7OBFDWytbzAC104u7rxHwGjA==
|
integrity sha512-iSB7eE3k/VNQHnI7ckS++4yIqTamoUCB1xo7MswhJ/fg22oFYR5+xCrUZVviBj97jvc5A31MPbVMw1Wc3jWxmw==
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.1.2"
|
fsevents "~2.1.2"
|
||||||
|
|
||||||
@ -8986,7 +9008,7 @@ yallist@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||||
|
|
||||||
yaml@^1.7.2:
|
yaml@^1.10.0, yaml@^1.7.2:
|
||||||
version "1.10.0"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
|
||||||
integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
|
integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
|
||||||
|
Loading…
Reference in New Issue
Block a user