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 DevicesTable from './metrics/DevicesTable';
|
||||
import CountriesTable from './metrics/CountriesTable';
|
||||
import EventsTable from './metrics/EventsTable';
|
||||
import EventsChart from './metrics/EventsChart';
|
||||
|
||||
export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' }) {
|
||||
const [data, setData] = useState();
|
||||
const [chartLoaded, setChartLoaded] = useState(false);
|
||||
const [countryData, setCountryData] = useState();
|
||||
const [eventsData, setEventsData] = useState();
|
||||
const [dateRange, setDateRange] = useState(getDateRange(defaultDateRange));
|
||||
const [expand, setExpand] = useState();
|
||||
const { startDate, endDate } = dateRange;
|
||||
const { startDate, endDate, unit } = dateRange;
|
||||
|
||||
const BackButton = () => (
|
||||
<Button
|
||||
@ -50,12 +53,18 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
|
||||
value: 'country',
|
||||
component: props => <CountriesTable {...props} onDataLoad={data => setCountryData(data)} />,
|
||||
},
|
||||
{ label: 'Events', value: 'event', component: EventsTable },
|
||||
];
|
||||
|
||||
const tableProps = {
|
||||
const dataProps = {
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
unit,
|
||||
};
|
||||
|
||||
const tableProps = {
|
||||
...dataProps,
|
||||
limit: 10,
|
||||
onExpand: handleExpand,
|
||||
websiteDomain: data?.domain,
|
||||
@ -63,6 +72,10 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
|
||||
|
||||
const DetailsComponent = expand?.component;
|
||||
|
||||
function getSelectedMenuOption(value) {
|
||||
return menuOptions.find(e => e.value === value);
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
setData(await get(`/api/website/${websiteId}`));
|
||||
}
|
||||
@ -76,11 +89,11 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
|
||||
}
|
||||
|
||||
function handleExpand(value) {
|
||||
setExpand(menuOptions.find(e => e.value === value));
|
||||
setExpand(getSelectedMenuOption(value));
|
||||
}
|
||||
|
||||
function handleMenuSelect(value) {
|
||||
setExpand(menuOptions.find(e => e.value === value));
|
||||
setExpand(getSelectedMenuOption(value));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@ -132,7 +145,17 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
|
||||
<WorldMap data={countryData} />
|
||||
</div>
|
||||
<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>
|
||||
</>
|
||||
|
@ -42,6 +42,10 @@
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
.row {
|
||||
border: 0;
|
||||
|
@ -2,13 +2,13 @@
|
||||
display: inline-flex;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--gray400);
|
||||
border: 1px solid var(--gray500);
|
||||
}
|
||||
|
||||
.group .button {
|
||||
border-radius: 0;
|
||||
background: var(--gray50);
|
||||
border-left: 1px solid var(--gray400);
|
||||
border-left: 1px solid var(--gray500);
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
@ -16,6 +16,10 @@
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.group .button:hover {
|
||||
background: var(--gray100);
|
||||
}
|
||||
|
||||
.group .button + .button {
|
||||
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 {
|
||||
position: relative;
|
||||
border-left: 1px solid var(--gray300);
|
||||
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="Bounce rate"
|
||||
value={uniques ? (bounces / uniques) * 100 : 0}
|
||||
value={pageviews ? (bounces / pageviews) * 100 : 0}
|
||||
format={n => Number(n).toFixed(0) + '%'}
|
||||
/>
|
||||
<MetricCard
|
||||
|
@ -8,6 +8,7 @@ import { get } from 'lib/web';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import { formatNumber, formatLongNumber } from 'lib/format';
|
||||
import styles from './MetricsTable.module.css';
|
||||
import Loading from '../common/Loading';
|
||||
|
||||
export default function MetricsTable({
|
||||
title,
|
||||
@ -21,9 +22,9 @@ export default function MetricsTable({
|
||||
filterOptions,
|
||||
limit,
|
||||
headerComponent,
|
||||
renderLabel,
|
||||
onDataLoad = () => {},
|
||||
onExpand = () => {},
|
||||
labelRenderer = e => e,
|
||||
}) {
|
||||
const [data, setData] = useState();
|
||||
const [format, setFormat] = useState(true);
|
||||
@ -43,37 +44,34 @@ export default function MetricsTable({
|
||||
|
||||
async function loadData() {
|
||||
const data = await get(`/api/website/${websiteId}/rankings`, {
|
||||
type,
|
||||
start_at: +startDate,
|
||||
end_at: +endDate,
|
||||
type,
|
||||
});
|
||||
|
||||
setData(data);
|
||||
onDataLoad(data);
|
||||
}
|
||||
|
||||
function handleSetFormat() {
|
||||
setFormat(state => !state);
|
||||
}
|
||||
const handleSetFormat = () => setFormat(state => !state);
|
||||
|
||||
function getRow(x, y, z) {
|
||||
const getRow = row => {
|
||||
const { x: label, y: value, z: percent } = row;
|
||||
return (
|
||||
<AnimatedRow
|
||||
key={x}
|
||||
label={x}
|
||||
value={y}
|
||||
percent={z}
|
||||
key={label}
|
||||
label={renderLabel ? renderLabel(row) : label}
|
||||
value={value}
|
||||
percent={percent}
|
||||
animate={shouldAnimate}
|
||||
format={formatFunc}
|
||||
onClick={handleSetFormat}
|
||||
labelRenderer={labelRenderer}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const Row = ({ index, style }) => {
|
||||
const { x, y, z } = rankings[index];
|
||||
return <div style={style}>{getRow(x, y, z)}</div>;
|
||||
return <div style={style}>{getRow(rankings[index])}</div>;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -82,12 +80,10 @@ export default function MetricsTable({
|
||||
}
|
||||
}, [websiteId, startDate, endDate, type]);
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.container, className)}>
|
||||
{data ? (
|
||||
<>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.title}>{title}</div>
|
||||
{headerComponent}
|
||||
@ -96,10 +92,10 @@ export default function MetricsTable({
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.body}>
|
||||
{limit ? (
|
||||
rankings.map(({ x, y, z }) => getRow(x, y, z))
|
||||
) : (
|
||||
<FixedSizeList height={600} itemCount={rankings.length} itemSize={30}>
|
||||
{limit
|
||||
? rankings.map(row => getRow(row))
|
||||
: data?.length > 0 && (
|
||||
<FixedSizeList height={500} itemCount={rankings.length} itemSize={30}>
|
||||
{Row}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
@ -111,11 +107,15 @@ export default function MetricsTable({
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const AnimatedRow = ({ label, value = 0, percent, animate, format, onClick, labelRenderer }) => {
|
||||
const AnimatedRow = ({ label, value = 0, percent, animate, format, onClick }) => {
|
||||
const props = useSpring({
|
||||
width: percent,
|
||||
y: value,
|
||||
@ -125,7 +125,7 @@ const AnimatedRow = ({ label, value = 0, percent, animate, format, onClick, labe
|
||||
|
||||
return (
|
||||
<div className={styles.row}>
|
||||
<div className={styles.label}>{labelRenderer(decodeURI(label))}</div>
|
||||
<div className={styles.label}>{label}</div>
|
||||
<div className={styles.value} onClick={onClick}>
|
||||
<animated.div className={styles.value}>{props.y?.interpolate(format)}</animated.div>
|
||||
</div>
|
||||
|
@ -25,6 +25,7 @@ export default function PagesTable({
|
||||
limit={limit}
|
||||
dataFilter={urlFilter}
|
||||
filterOptions={{ domain: websiteDomain, raw: filter === 'Raw' }}
|
||||
renderLabel={({ x }) => decodeURI(x)}
|
||||
onExpand={onExpand}
|
||||
/>
|
||||
);
|
||||
|
@ -1,67 +1,30 @@
|
||||
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
import classNames from 'classnames';
|
||||
import ChartJS from 'chart.js';
|
||||
import { format } from 'date-fns';
|
||||
import styles from './PageviewsChart.module.css';
|
||||
import React from 'react';
|
||||
import CheckVisible from 'components/helpers/CheckVisible';
|
||||
import BarChart from './BarChart';
|
||||
|
||||
export default function PageviewsChart({
|
||||
websiteId,
|
||||
data,
|
||||
unit,
|
||||
animationDuration = 300,
|
||||
className,
|
||||
children,
|
||||
}) {
|
||||
const canvas = useRef();
|
||||
const chart = useRef();
|
||||
const [tooltip, setTooltip] = useState({});
|
||||
export default function PageviewsChart({ websiteId, data, unit, className }) {
|
||||
const handleUpdate = chart => {
|
||||
const {
|
||||
data: { datasets },
|
||||
} = chart;
|
||||
|
||||
const renderLabel = useCallback(
|
||||
(label, index, values) => {
|
||||
const d = new Date(values[index].value);
|
||||
const n = data.pageviews.length;
|
||||
datasets[0].data = data.uniques;
|
||||
datasets[1].data = data.pageviews;
|
||||
|
||||
switch (unit) {
|
||||
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,
|
||||
});
|
||||
}
|
||||
chart.update();
|
||||
};
|
||||
|
||||
function draw() {
|
||||
if (!canvas.current) return;
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!chart.current) {
|
||||
chart.current = new ChartJS(canvas.current, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
datasets: [
|
||||
return (
|
||||
<CheckVisible>
|
||||
{visible => (
|
||||
<BarChart
|
||||
className={className}
|
||||
chartId={websiteId}
|
||||
datasets={[
|
||||
{
|
||||
label: 'unique visitors',
|
||||
data: data.uniques,
|
||||
@ -78,97 +41,13 @@ export default function PageviewsChart({
|
||||
borderColor: 'rgb(13, 102, 208, 0.2)',
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
animation: {
|
||||
duration: animationDuration,
|
||||
},
|
||||
tooltips: {
|
||||
enabled: false,
|
||||
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>
|
||||
]}
|
||||
unit={unit}
|
||||
records={data.pageviews.length}
|
||||
animationDuration={visible ? 300 : 0}
|
||||
onUpdate={handleUpdate}
|
||||
/>
|
||||
)}
|
||||
</CheckVisible>
|
||||
);
|
||||
}
|
||||
|
||||
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 renderLink = url => {
|
||||
const renderLink = ({ x: url }) => {
|
||||
return url.startsWith('http') ? (
|
||||
<a href={url} target="_blank" rel="noreferrer">
|
||||
{url}
|
||||
{decodeURI(url)}
|
||||
</a>
|
||||
) : (
|
||||
url
|
||||
decodeURI(url)
|
||||
);
|
||||
};
|
||||
|
||||
@ -40,7 +40,7 @@ export default function Referrers({
|
||||
raw: filter === 'Raw',
|
||||
}}
|
||||
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 PageviewsChart from './PageviewsChart';
|
||||
import CheckVisible from '../helpers/CheckVisible';
|
||||
import MetricsBar from './MetricsBar';
|
||||
import QuickButtons from './QuickButtons';
|
||||
import DateFilter from '../common/DateFilter';
|
||||
@ -74,18 +73,10 @@ export default function WebsiteChart({
|
||||
</StickyHeader>
|
||||
</div>
|
||||
<div className="row">
|
||||
<CheckVisible className="col">
|
||||
{visible => (
|
||||
<PageviewsChart
|
||||
websiteId={websiteId}
|
||||
data={{ pageviews, uniques }}
|
||||
unit={unit}
|
||||
animationDuration={visible ? 300 : 0}
|
||||
>
|
||||
<div className="col">
|
||||
<PageviewsChart websiteId={websiteId} data={{ pageviews, uniques }} unit={unit} />
|
||||
<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) {
|
||||
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();
|
||||
});
|
||||
|
||||
@ -108,8 +114,13 @@ export function getDateArray(data, startDate, endDate, unit) {
|
||||
const t = add(startDate, i);
|
||||
const y = findData(t);
|
||||
|
||||
arr.push({ t, y });
|
||||
arr.push({ ...data[i], t, y });
|
||||
}
|
||||
|
||||
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) {
|
||||
return query.catch(e => {
|
||||
console.error(e);
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
@ -49,12 +49,13 @@ export const urlFilter = (data, { domain, raw }) => {
|
||||
};
|
||||
|
||||
export const refFilter = (data, { domain, domainOnly, raw }) => {
|
||||
const regex = new RegExp(domain.startsWith('http') ? domain : `http[s]?://${domain}`);
|
||||
|
||||
const isValidRef = ref => {
|
||||
return ref !== '' && !ref.startsWith('/') && !ref.startsWith('#');
|
||||
};
|
||||
|
||||
if (raw) {
|
||||
const regex = new RegExp(`http[s]?://([^.]+.)?${domain}`);
|
||||
return data.filter(({ x }) => isValidRef(x) && !regex.test(x));
|
||||
}
|
||||
|
||||
@ -62,7 +63,7 @@ export const refFilter = (data, { domain, domainOnly, raw }) => {
|
||||
try {
|
||||
const { hostname, origin, pathname, searchParams, protocol } = new URL(url);
|
||||
|
||||
if (hostname === domain) {
|
||||
if (hostname === domain || regex.test(url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -124,5 +125,5 @@ export const countryFilter = data =>
|
||||
|
||||
export const percentFilter = data => {
|
||||
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 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() {
|
||||
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) {
|
||||
return runQuery(
|
||||
prisma.website.findOne({
|
||||
@ -236,7 +254,7 @@ export function getMetrics(website_id, start_at, end_at) {
|
||||
`
|
||||
select sum(t.c) as "pageviews",
|
||||
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"
|
||||
from (
|
||||
select session_id,
|
||||
@ -260,11 +278,11 @@ export function getMetrics(website_id, start_at, end_at) {
|
||||
`
|
||||
select sum(t.c) as "pageviews",
|
||||
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"
|
||||
from (
|
||||
select session_id,
|
||||
date_trunc('hour', created_at),
|
||||
${getDateQuery('created_at', 'hour')},
|
||||
count(*) c,
|
||||
floor(unix_timestamp(max(created_at)) - unix_timestamp(min(created_at))) as "time"
|
||||
from pageview
|
||||
@ -310,10 +328,9 @@ export function getPageviews(
|
||||
}
|
||||
|
||||
if (db === MYSQL) {
|
||||
const tz = moment.tz(timezone).format('Z');
|
||||
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
|
||||
from pageview
|
||||
where website_id=?
|
||||
@ -400,3 +417,47 @@ export function getActiveVisitors(website_id) {
|
||||
|
||||
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",
|
||||
"version": "0.16.3",
|
||||
"version": "0.17.0",
|
||||
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
|
||||
"author": "Mike Cao <mike@mikecao.com>",
|
||||
"license": "MIT",
|
||||
@ -45,7 +45,7 @@
|
||||
"classnames": "^2.2.6",
|
||||
"cookie": "^0.4.1",
|
||||
"cors": "^2.8.5",
|
||||
"date-fns": "^2.15.0",
|
||||
"date-fns": "^2.16.0",
|
||||
"date-fns-tz": "^1.0.10",
|
||||
"detect-browser": "^5.1.1",
|
||||
"dotenv": "^8.2.0",
|
||||
@ -62,7 +62,7 @@
|
||||
"react-redux": "^7.2.1",
|
||||
"react-simple-maps": "^2.1.2",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-tooltip": "^4.2.8",
|
||||
"react-tooltip": "^4.2.9",
|
||||
"react-window": "^1.8.5",
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
@ -86,14 +86,14 @@
|
||||
"eslint-plugin-react": "^7.20.6",
|
||||
"eslint-plugin-react-hooks": "^4.1.0",
|
||||
"husky": "^4.2.5",
|
||||
"lint-staged": "^10.2.9",
|
||||
"lint-staged": "^10.2.13",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss-flexbugs-fixes": "^4.2.1",
|
||||
"postcss-import": "^12.0.1",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"prettier": "^2.0.5",
|
||||
"prettier": "^2.1.1",
|
||||
"prettier-eslint": "^11.0.0",
|
||||
"rollup": "^2.26.5",
|
||||
"rollup": "^2.26.6",
|
||||
"rollup-plugin-hashbang": "^2.2.2",
|
||||
"rollup-plugin-terser": "^7.0.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);
|
||||
}
|
||||
|
||||
const start = new Date(+start_at);
|
||||
const end = new Date(+end_at);
|
||||
const websiteId = +id;
|
||||
const startDate = new Date(+start_at);
|
||||
const endDate = new Date(+end_at);
|
||||
|
||||
const [pageviews, uniques] = await Promise.all([
|
||||
getPageviews(+id, start, end, tz, unit, '*'),
|
||||
getPageviews(+id, start, end, tz, unit, 'distinct session_id'),
|
||||
getPageviews(websiteId, startDate, endDate, tz, unit, '*'),
|
||||
getPageviews(websiteId, startDate, endDate, tz, unit, 'distinct session_id'),
|
||||
]);
|
||||
|
||||
return ok(res, { pageviews, uniques });
|
||||
|
@ -4,16 +4,42 @@ import { ok, badRequest } from 'lib/response';
|
||||
const sessionColumns = ['browser', 'os', 'device', 'country'];
|
||||
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) => {
|
||||
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);
|
||||
}
|
||||
|
||||
const table = sessionColumns.includes(type) ? 'session' : 'pageview';
|
||||
|
||||
const rankings = await getRankings(+id, new Date(+start_at), new Date(+end_at), type, table);
|
||||
const rankings = await getRankings(
|
||||
websiteId,
|
||||
startDate,
|
||||
endDate,
|
||||
getColumn(type),
|
||||
getTable(type),
|
||||
);
|
||||
|
||||
return ok(res, rankings);
|
||||
};
|
||||
|
@ -3,7 +3,6 @@ drop table if exists pageview;
|
||||
drop table if exists session;
|
||||
drop table if exists website;
|
||||
drop table if exists account;
|
||||
drop function if exists date_trunc;
|
||||
|
||||
create table account (
|
||||
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_website_id_idx on pageview(website_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_website_id_idx on event(website_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);
|
@ -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_website_id_idx on pageview(website_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_website_id_idx on event(website_id);
|
||||
|
186
yarn.lock
186
yarn.lock
@ -1086,7 +1086,7 @@
|
||||
levenary "^1.1.1"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72"
|
||||
integrity sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==
|
||||
@ -1097,6 +1097,17 @@
|
||||
"@babel/types" "^7.4.4"
|
||||
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":
|
||||
version "7.9.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.9.4.tgz#c6c97693ac65b6b9c0b4f25b948a8f665463014d"
|
||||
@ -1458,9 +1469,9 @@
|
||||
integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=
|
||||
|
||||
"@types/node@*":
|
||||
version "14.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.0.tgz#7d4411bf5157339337d7cff864d9ff45f177b499"
|
||||
integrity sha512-mikldZQitV94akrc4sCcSjtJfsTKt4p+e/s0AGscVA6XArQ9kFclP+ZiYUMnq987rc6QlYxXv/EivqlfSLxpKA==
|
||||
version "14.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.1.tgz#fdf6f6c6c73d3d8eee9c98a9a0485bc524b048d7"
|
||||
integrity sha512-HnYlg/BRF8uC1FyKRFZwRaCPTPYKa+6I8QiUZFLredaGOou481cgFS4wKRFyKvQtX8xudqkSdBczJHIYSQYKrQ==
|
||||
|
||||
"@types/normalize-package-data@^2.4.0":
|
||||
version "2.4.0"
|
||||
@ -1489,40 +1500,40 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
|
||||
integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
|
||||
|
||||
"@typescript-eslint/experimental-utils@3.9.1":
|
||||
version "3.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.9.1.tgz#b140b2dc7a7554a44f8a86fb6fe7cbfe57ca059e"
|
||||
integrity sha512-lkiZ8iBBaYoyEKhCkkw4SAeatXyBq9Ece5bZXdLe1LWBUwTszGbmbiqmQbwWA8cSYDnjWXp9eDbXpf9Sn0hLAg==
|
||||
"@typescript-eslint/experimental-utils@3.10.1":
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686"
|
||||
integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.3"
|
||||
"@typescript-eslint/types" "3.9.1"
|
||||
"@typescript-eslint/typescript-estree" "3.9.1"
|
||||
"@typescript-eslint/types" "3.10.1"
|
||||
"@typescript-eslint/typescript-estree" "3.10.1"
|
||||
eslint-scope "^5.0.0"
|
||||
eslint-utils "^2.0.0"
|
||||
|
||||
"@typescript-eslint/parser@^3.0.0":
|
||||
version "3.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.9.1.tgz#ab7983abaea0ae138ff5671c7c7739d8a191b181"
|
||||
integrity sha512-y5QvPFUn4Vl4qM40lI+pNWhTcOWtpZAJ8pOEQ21fTTW4xTJkRplMjMRje7LYTXqVKKX9GJhcyweMz2+W1J5bMg==
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467"
|
||||
integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==
|
||||
dependencies:
|
||||
"@types/eslint-visitor-keys" "^1.0.0"
|
||||
"@typescript-eslint/experimental-utils" "3.9.1"
|
||||
"@typescript-eslint/types" "3.9.1"
|
||||
"@typescript-eslint/typescript-estree" "3.9.1"
|
||||
"@typescript-eslint/experimental-utils" "3.10.1"
|
||||
"@typescript-eslint/types" "3.10.1"
|
||||
"@typescript-eslint/typescript-estree" "3.10.1"
|
||||
eslint-visitor-keys "^1.1.0"
|
||||
|
||||
"@typescript-eslint/types@3.9.1":
|
||||
version "3.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.9.1.tgz#b2a6eaac843cf2f2777b3f2464fb1fbce5111416"
|
||||
integrity sha512-15JcTlNQE1BsYy5NBhctnEhEoctjXOjOK+Q+rk8ugC+WXU9rAcS2BYhoh6X4rOaXJEpIYDl+p7ix+A5U0BqPTw==
|
||||
"@typescript-eslint/types@3.10.1":
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727"
|
||||
integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==
|
||||
|
||||
"@typescript-eslint/typescript-estree@3.9.1":
|
||||
version "3.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.9.1.tgz#fd81cada74bc8a7f3a2345b00897acb087935779"
|
||||
integrity sha512-IqM0gfGxOmIKPhiHW/iyAEXwSVqMmR2wJ9uXHNdFpqVvPaQ3dWg302vW127sBpAiqM9SfHhyS40NKLsoMpN2KA==
|
||||
"@typescript-eslint/typescript-estree@3.10.1":
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853"
|
||||
integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "3.9.1"
|
||||
"@typescript-eslint/visitor-keys" "3.9.1"
|
||||
"@typescript-eslint/types" "3.10.1"
|
||||
"@typescript-eslint/visitor-keys" "3.10.1"
|
||||
debug "^4.1.1"
|
||||
glob "^7.1.6"
|
||||
is-glob "^4.0.1"
|
||||
@ -1530,10 +1541,10 @@
|
||||
semver "^7.3.2"
|
||||
tsutils "^3.17.1"
|
||||
|
||||
"@typescript-eslint/visitor-keys@3.9.1":
|
||||
version "3.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.9.1.tgz#92af3747cdb71509199a8f7a4f00b41d636551d1"
|
||||
integrity sha512-zxdtUjeoSh+prCpogswMwVUJfEFmCOjdzK9rpNjNBfm6EyPt99x3RrJoBOGZO23FCt0WPKUCOL5mb/9D5LjdwQ==
|
||||
"@typescript-eslint/visitor-keys@3.10.1":
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931"
|
||||
integrity sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==
|
||||
dependencies:
|
||||
eslint-visitor-keys "^1.1.0"
|
||||
|
||||
@ -2352,9 +2363,9 @@ camelcase@^6.0.0:
|
||||
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:
|
||||
version "1.0.30001117"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001117.tgz#69a9fae5d480eaa9589f7641a83842ad396d17c4"
|
||||
integrity sha512-4tY0Fatzdx59kYjQs+bNxUwZB03ZEBgVmJ1UkFPz/Q8OLiUUbjct2EdpnXj0fvFTPej2EkbPIG0w8BWsjAyk1Q==
|
||||
version "1.0.30001119"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001119.tgz#99185d04bc00e76a86c9ff731dc5ec8e53aefca1"
|
||||
integrity sha512-Hpwa4obv7EGP+TjkCh/wVvbtNJewxmtg4yVJBLFnxo35vbPapBr138bUWENkb5j5L9JZJ9RXLn4OrXRG/cecPQ==
|
||||
|
||||
ccount@^1.0.0:
|
||||
version "1.0.5"
|
||||
@ -2536,7 +2547,7 @@ cli-cursor@^3.1.0:
|
||||
dependencies:
|
||||
restore-cursor "^3.1.0"
|
||||
|
||||
cli-truncate@2.1.0, cli-truncate@^2.1.0:
|
||||
cli-truncate@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7"
|
||||
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"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
|
||||
integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
|
||||
commander@^6.0.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-6.1.0.tgz#f8d722b78103141006b66f4c7ba1e97315ba75bc"
|
||||
integrity sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA==
|
||||
|
||||
common-tags@^1.4.0:
|
||||
version "1.8.0"
|
||||
@ -2765,6 +2776,17 @@ cosmiconfig@^6.0.0:
|
||||
path-type "^4.0.0"
|
||||
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:
|
||||
version "4.0.4"
|
||||
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"
|
||||
integrity sha512-cHQAz0/9uDABaUNDM80Mj1FL4ODlxs1xEY4b0DQuAooO2UdNKvDkNbV8ogLnxLbv02Ru1HXFcot0pVvDRBgptg==
|
||||
|
||||
date-fns@^2.15.0:
|
||||
version "2.15.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.15.0.tgz#424de6b3778e4e69d3ff27046ec136af58ae5d5f"
|
||||
integrity sha512-ZCPzAMJZn3rNUvvQIMlXhDr4A+Ar07eLeGsGREoWU19a3Pqf5oYa+ccd+B3F6XVtQY6HANMFdOQ8A+ipFnvJdQ==
|
||||
date-fns@^2.16.0:
|
||||
version "2.16.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.0.tgz#d34f0f5f2fd498c984513042e8f7247ea86c4cb7"
|
||||
integrity sha512-DWTRyfOA85sZ4IiXPHhiRIOs3fW5U6Msrp+gElXARa6EpoQTXPyHQmh7hr+ssw2nx9FtOQWnAMJKgL5vaJqILw==
|
||||
|
||||
debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
|
||||
version "4.1.1"
|
||||
@ -3351,9 +3373,9 @@ duplexify@^3.4.2, duplexify@^3.6.0:
|
||||
stream-shift "^1.0.0"
|
||||
|
||||
electron-to-chromium@^1.3.488, electron-to-chromium@^1.3.523:
|
||||
version "1.3.544"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.544.tgz#ac1f7d319f6060f3d6d122261d542ec77eb1427e"
|
||||
integrity sha512-jx6H7M1db76Q/dI3MadZC4qwNTvpiq8tdYEJswxexrIm5bH+LKRdg+VAteMF1tJJbBLrcuogE9N3nxT3Dp1gag==
|
||||
version "1.3.553"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.553.tgz#c4693d8660470a3aa830907890e446a9f3b26376"
|
||||
integrity sha512-wi/hoMuTGK6OJoLOHqmXFA9BWOQGF2nInCfk+/Owhd4VVfuenKE2LZr9TtFCmwyda2SE9hG+sRnqRCwhYgFeIg==
|
||||
|
||||
elliptic@^6.5.3:
|
||||
version "6.5.3"
|
||||
@ -3404,7 +3426,7 @@ enhanced-resolve@^4.3.0:
|
||||
memory-fs "^0.5.0"
|
||||
tapable "^1.0.0"
|
||||
|
||||
enquirer@^2.3.5:
|
||||
enquirer@^2.3.5, enquirer@^2.3.6:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
|
||||
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"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
execa@^4.0.1:
|
||||
execa@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2"
|
||||
integrity sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==
|
||||
@ -4435,11 +4457,11 @@ ignore@^5.1.4, ignore@^5.1.8:
|
||||
integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
|
||||
|
||||
immer@^7.0.3:
|
||||
version "7.0.7"
|
||||
resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.7.tgz#9dfe713d49bf871cc59aedfce59b1992fa37a977"
|
||||
integrity sha512-Q8yYwVADJXrNfp1ZUAh4XDHkcoE3wpdpb4mC5abDSajs2EbW8+cGdPyAnglMyLnm7EF6ojD2xBFX7L5i4TIytw==
|
||||
version "7.0.8"
|
||||
resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.8.tgz#41dcbc5669a76500d017bef3ad0d03ce0a1d7c1e"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66"
|
||||
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"
|
||||
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
|
||||
|
||||
lint-staged@^10.2.9:
|
||||
version "10.2.11"
|
||||
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.11.tgz#713c80877f2dc8b609b05bc59020234e766c9720"
|
||||
integrity sha512-LRRrSogzbixYaZItE2APaS4l2eJMjjf5MbclRZpLJtcQJShcvUzKXsNeZgsLIZ0H0+fg2tL4B59fU9wHIHtFIA==
|
||||
lint-staged@^10.2.13:
|
||||
version "10.2.13"
|
||||
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.13.tgz#b9c504683470edfc464b7d3fe3845a5a1efcd814"
|
||||
integrity sha512-conwlukNV6aL9SiMWjFtDp5exeDnTMekdNPDZsKGnpfQuHcO0E3L3Bbf58lcR+M7vk6LpCilxDAVks/DDVBYlA==
|
||||
dependencies:
|
||||
chalk "^4.0.0"
|
||||
cli-truncate "2.1.0"
|
||||
commander "^5.1.0"
|
||||
cosmiconfig "^6.0.0"
|
||||
chalk "^4.1.0"
|
||||
cli-truncate "^2.1.0"
|
||||
commander "^6.0.0"
|
||||
cosmiconfig "^7.0.0"
|
||||
debug "^4.1.1"
|
||||
dedent "^0.7.0"
|
||||
enquirer "^2.3.5"
|
||||
execa "^4.0.1"
|
||||
listr2 "^2.1.0"
|
||||
enquirer "^2.3.6"
|
||||
execa "^4.0.3"
|
||||
listr2 "^2.6.0"
|
||||
log-symbols "^4.0.0"
|
||||
micromatch "^4.0.2"
|
||||
normalize-path "^3.0.0"
|
||||
@ -5020,10 +5042,10 @@ lint-staged@^10.2.9:
|
||||
string-argv "0.3.1"
|
||||
stringify-object "^3.3.0"
|
||||
|
||||
listr2@^2.1.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.6.0.tgz#788a3d202978a1b8582062952cbc49272c8e206a"
|
||||
integrity sha512-nwmqTJYQQ+AsKb4fCXH/6/UmLCEDL1jkRAdSn9M6cEUzoRGrs33YD/3N86gAZQnGZ6hxV18XSdlBcJ1GTmetJA==
|
||||
listr2@^2.6.0:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.6.1.tgz#fbbabd8eea723924df7530042c1990b346e81706"
|
||||
integrity sha512-1aPX9GkS+W0aHfPUDedJqeqj0DOe1605NaNoqdwEYw/UF2UbZgCIIMpXXZALeG/8xzwMBztguzQEubU5Xw1Qbw==
|
||||
dependencies:
|
||||
chalk "^4.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:
|
||||
version "1.6.8"
|
||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171"
|
||||
integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0"
|
||||
integrity sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ==
|
||||
|
||||
longest-streak@^2.0.1:
|
||||
version "2.0.4"
|
||||
@ -6779,10 +6801,10 @@ prettier-linter-helpers@^1.0.0:
|
||||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@^2.0.0, prettier@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4"
|
||||
integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==
|
||||
prettier@^2.0.0, prettier@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.1.tgz#d9485dd5e499daa6cb547023b87a6cf51bee37d6"
|
||||
integrity sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw==
|
||||
|
||||
pretty-format@^23.0.1:
|
||||
version "23.6.0"
|
||||
@ -6999,10 +7021,10 @@ react-spring@^8.0.27:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
prop-types "^15.5.8"
|
||||
|
||||
react-tooltip@^4.2.8:
|
||||
version "4.2.8"
|
||||
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.8.tgz#270858fee46fab73b66de316271aa94145f7446b"
|
||||
integrity sha512-pDWa0/khTAgIfldp95tHgyuYyBhWNlfaU2LF9ubAKxpoqNe15uyf+uLlnhK/Lstb6FU8E8/SL28Wp6oEO9xw3g==
|
||||
react-tooltip@^4.2.9:
|
||||
version "4.2.9"
|
||||
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.9.tgz#0dd08d14191f5d0e56b51c822fa20c2d81a24272"
|
||||
integrity sha512-DgZyg5oxk9/orgePDLLeuDtlwwYv7CalJRahk9nNsoEJDzIO58GC6zSAet4bKTm6c01hg1z3EocP9H0nmMHTMA==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
uuid "^7.0.3"
|
||||
@ -7417,10 +7439,10 @@ rollup-plugin-terser@^7.0.0:
|
||||
serialize-javascript "^4.0.0"
|
||||
terser "^5.0.0"
|
||||
|
||||
rollup@^2.26.5:
|
||||
version "2.26.5"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.26.5.tgz#5562ec36fcba3eed65cfd630bd78e037ad0e0307"
|
||||
integrity sha512-rCyFG3ZtQdnn9YwfuAVH0l/Om34BdO5lwCA0W6Hq+bNB21dVEBbCRxhaHOmu1G7OBFDWytbzAC104u7rxHwGjA==
|
||||
rollup@^2.26.6:
|
||||
version "2.26.6"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.26.6.tgz#0b460c1da224c6af12a1e948a28c513aa11f2b93"
|
||||
integrity sha512-iSB7eE3k/VNQHnI7ckS++4yIqTamoUCB1xo7MswhJ/fg22oFYR5+xCrUZVviBj97jvc5A31MPbVMw1Wc3jWxmw==
|
||||
optionalDependencies:
|
||||
fsevents "~2.1.2"
|
||||
|
||||
@ -8986,7 +9008,7 @@ yallist@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yaml@^1.7.2:
|
||||
yaml@^1.10.0, yaml@^1.7.2:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
|
||||
integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
|
||||
|
Loading…
Reference in New Issue
Block a user