You've already forked AstralRinth
forked from didirus/AstralRinth
refactor(knossos): Rewrite date range system on analytics dashboard. (#1301)
* Start work on refactoring date range system. * Use timeResolution terminology. * "Last month" initial default. * Migrate fully to dayjs - ease of use. * Discard changes to pnpm-lock.yaml * utilize getter * Fix date label in ChartDisplay.vue * Finish cleanup * Update STAGING_API_URL in nuxt.config.ts * Lint fixes * Refactor ChartDisplay.vue to handle loading state in selectedRange and formattedCategorySubtitle * Remove modal impl --------- Signed-off-by: Calum H. <contact@mineblock11.dev> Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
<CompactChart
|
<CompactChart
|
||||||
v-if="analytics.formattedData.value.downloads"
|
v-if="analytics.formattedData.value.downloads"
|
||||||
ref="tinyDownloadChart"
|
ref="tinyDownloadChart"
|
||||||
:title="`Downloads since ${dayjs(startDate).format('MMM D, YYYY')}`"
|
:title="`Downloads`"
|
||||||
color="var(--color-brand)"
|
color="var(--color-brand)"
|
||||||
:value="formatNumber(analytics.formattedData.value.downloads.sum, false)"
|
:value="formatNumber(analytics.formattedData.value.downloads.sum, false)"
|
||||||
:data="analytics.formattedData.value.downloads.chart.sumData"
|
:data="analytics.formattedData.value.downloads.chart.sumData"
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<CompactChart
|
<CompactChart
|
||||||
v-if="analytics.formattedData.value.views"
|
v-if="analytics.formattedData.value.views"
|
||||||
ref="tinyViewChart"
|
ref="tinyViewChart"
|
||||||
:title="`Page views since ${dayjs(startDate).format('MMM D, YYYY')}`"
|
:title="`Views`"
|
||||||
color="var(--color-blue)"
|
color="var(--color-blue)"
|
||||||
:value="formatNumber(analytics.formattedData.value.views.sum, false)"
|
:value="formatNumber(analytics.formattedData.value.views.sum, false)"
|
||||||
:data="analytics.formattedData.value.views.chart.sumData"
|
:data="analytics.formattedData.value.views.chart.sumData"
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
<CompactChart
|
<CompactChart
|
||||||
v-if="analytics.formattedData.value.revenue"
|
v-if="analytics.formattedData.value.revenue"
|
||||||
ref="tinyRevenueChart"
|
ref="tinyRevenueChart"
|
||||||
:title="`Revenue since ${dayjs(startDate).format('MMM D, YYYY')}`"
|
:title="`Revenue`"
|
||||||
color="var(--color-purple)"
|
color="var(--color-purple)"
|
||||||
:value="formatMoney(analytics.formattedData.value.revenue.sum, false)"
|
:value="formatMoney(analytics.formattedData.value.revenue.sum, false)"
|
||||||
:data="analytics.formattedData.value.revenue.chart.sumData"
|
:data="analytics.formattedData.value.revenue.chart.sumData"
|
||||||
@@ -71,6 +71,9 @@
|
|||||||
<span class="label__title">
|
<span class="label__title">
|
||||||
{{ formatCategoryHeader(selectedChart) }}
|
{{ formatCategoryHeader(selectedChart) }}
|
||||||
</span>
|
</span>
|
||||||
|
<span class="label__subtitle">
|
||||||
|
{{ formattedCategorySubtitle }}
|
||||||
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="chart-controls__buttons">
|
<div class="chart-controls__buttons">
|
||||||
<Button v-tooltip="'Toggle project colors'" icon-only @click="onToggleColors">
|
<Button v-tooltip="'Toggle project colors'" icon-only @click="onToggleColors">
|
||||||
@@ -83,11 +86,12 @@
|
|||||||
<UpdatedIcon />
|
<UpdatedIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
|
class="range-dropdown"
|
||||||
v-model="selectedRange"
|
v-model="selectedRange"
|
||||||
:options="selectableRanges"
|
:options="ranges"
|
||||||
name="Time range"
|
name="Time range"
|
||||||
:display-name="
|
:display-name="
|
||||||
(o: (typeof selectableRanges)[number] | undefined) => o?.label || 'Custom'
|
(o: RangeObject) => o?.getLabel([startDate, endDate]) ?? 'Loading...'
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -322,7 +326,7 @@ const props = withDefaults(
|
|||||||
* @deprecated Use `ranges` instead
|
* @deprecated Use `ranges` instead
|
||||||
*/
|
*/
|
||||||
resoloutions?: Record<string, number>;
|
resoloutions?: Record<string, number>;
|
||||||
ranges?: Record<number, [string, number] | string>;
|
ranges?: RangeObject[];
|
||||||
personal?: boolean;
|
personal?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
@@ -335,12 +339,6 @@ const props = withDefaults(
|
|||||||
|
|
||||||
const projects = ref(props.projects || []);
|
const projects = ref(props.projects || []);
|
||||||
|
|
||||||
const selectableRanges = Object.entries(props.ranges).map(([duration, extra]) => ({
|
|
||||||
label: typeof extra === "string" ? extra : extra[0],
|
|
||||||
value: Number(duration),
|
|
||||||
res: typeof extra === "string" ? Number(duration) : extra[1],
|
|
||||||
}));
|
|
||||||
|
|
||||||
// const selectedChart = ref('downloads')
|
// const selectedChart = ref('downloads')
|
||||||
const selectedChart = computed({
|
const selectedChart = computed({
|
||||||
get: () => {
|
get: () => {
|
||||||
@@ -413,33 +411,78 @@ const isUsingProjectColors = computed({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const startDate = ref(dayjs().startOf("day"));
|
||||||
|
const endDate = ref(dayjs().endOf("day"));
|
||||||
|
const timeResolution = ref(30);
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
// Load cached data and range from localStorage - cache.
|
||||||
|
if (import.meta.client) {
|
||||||
|
const rangeLabel = localStorage.getItem("analyticsSelectedRange");
|
||||||
|
if (rangeLabel) {
|
||||||
|
const range = props.ranges.find((r) => r.getLabel([dayjs(), dayjs()]) === rangeLabel)!;
|
||||||
|
|
||||||
|
if (range !== undefined) {
|
||||||
|
internalRange.value = range;
|
||||||
|
const ranges = range.getDates(dayjs());
|
||||||
|
timeResolution.value = range.timeResolution;
|
||||||
|
startDate.value = ranges.startDate;
|
||||||
|
endDate.value = ranges.endDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (internalRange.value === null) {
|
||||||
|
internalRange.value = props.ranges.find(
|
||||||
|
(r) => r.getLabel([dayjs(), dayjs()]) === "Previous 30 days",
|
||||||
|
)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ranges = selectedRange.value.getDates(dayjs());
|
||||||
|
startDate.value = ranges.startDate;
|
||||||
|
endDate.value = ranges.endDate;
|
||||||
|
timeResolution.value = selectedRange.value.timeResolution;
|
||||||
|
});
|
||||||
|
|
||||||
|
const internalRange: Ref<RangeObject> = ref(null as unknown as RangeObject);
|
||||||
|
|
||||||
|
const selectedRange = computed({
|
||||||
|
get: () => {
|
||||||
|
return internalRange.value;
|
||||||
|
},
|
||||||
|
set: (newRange) => {
|
||||||
|
const ranges = newRange.getDates(dayjs());
|
||||||
|
startDate.value = ranges.startDate;
|
||||||
|
endDate.value = ranges.endDate;
|
||||||
|
timeResolution.value = newRange.timeResolution;
|
||||||
|
|
||||||
|
internalRange.value = newRange;
|
||||||
|
|
||||||
|
if (import.meta.client) {
|
||||||
|
localStorage.setItem(
|
||||||
|
"analyticsSelectedRange",
|
||||||
|
internalRange.value?.getLabel([dayjs(), dayjs()]) ?? "Previous 30 days",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const analytics = useFetchAllAnalytics(
|
const analytics = useFetchAllAnalytics(
|
||||||
resetCharts,
|
resetCharts,
|
||||||
projects,
|
projects,
|
||||||
selectedDisplayProjects,
|
selectedDisplayProjects,
|
||||||
props.personal,
|
props.personal,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
timeResolution,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { startDate, endDate, timeRange, timeResolution } = analytics;
|
const formattedCategorySubtitle = computed(() => {
|
||||||
|
return (
|
||||||
const selectedRange = computed({
|
selectedRange.value?.getLabel([dayjs(startDate.value), dayjs(endDate.value)]) ?? "Loading..."
|
||||||
get: () => {
|
);
|
||||||
return (
|
|
||||||
selectableRanges.find((option) => option.value === timeRange.value) || {
|
|
||||||
label: "Custom",
|
|
||||||
value: timeRange.value,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
set: (newRange: { label: string; value: number; res?: number }) => {
|
|
||||||
timeRange.value = newRange.value;
|
|
||||||
startDate.value = Date.now() - timeRange.value * 60 * 1000;
|
|
||||||
endDate.value = Date.now();
|
|
||||||
|
|
||||||
if (newRange?.res) {
|
|
||||||
timeResolution.value = newRange.res;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedDataSet = computed(() => {
|
const selectedDataSet = computed(() => {
|
||||||
@@ -484,6 +527,9 @@ const onToggleColors = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* @deprecated Use `ranges` instead
|
||||||
|
*/
|
||||||
const defaultResoloutions: Record<string, number> = {
|
const defaultResoloutions: Record<string, number> = {
|
||||||
"5 minutes": 5,
|
"5 minutes": 5,
|
||||||
"30 minutes": 30,
|
"30 minutes": 30,
|
||||||
@@ -493,17 +539,161 @@ const defaultResoloutions: Record<string, number> = {
|
|||||||
"A week": 10080,
|
"A week": 10080,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultRanges: Record<number, [string, number] | string> = {
|
type DateRange = { startDate: dayjs.Dayjs; endDate: dayjs.Dayjs };
|
||||||
30: ["Last 30 minutes", 1],
|
|
||||||
60: ["Last hour", 5],
|
type RangeObject = {
|
||||||
720: ["Last 12 hours", 15],
|
getLabel: (dateRange: [dayjs.Dayjs, dayjs.Dayjs]) => string;
|
||||||
1440: ["Last day", 60],
|
getDates: (currentDate: dayjs.Dayjs) => DateRange;
|
||||||
10080: ["Last week", 720],
|
// A time resolution in minutes.
|
||||||
43200: ["Last month", 1440],
|
timeResolution: number;
|
||||||
129600: ["Last quarter", 10080],
|
|
||||||
525600: ["Last year", 20160],
|
|
||||||
1051200: ["Last two years", 40320],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultRanges: RangeObject[] = [
|
||||||
|
{
|
||||||
|
getLabel: () => "Previous 30 minutes",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).subtract(30, "minute"),
|
||||||
|
endDate: currentDate,
|
||||||
|
}),
|
||||||
|
timeResolution: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "Previous hour",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).subtract(1, "hour"),
|
||||||
|
endDate: currentDate,
|
||||||
|
}),
|
||||||
|
timeResolution: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "Previous 12 hours",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).subtract(12, "hour"),
|
||||||
|
endDate: currentDate,
|
||||||
|
}),
|
||||||
|
timeResolution: 12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "Previous 24 hours",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).subtract(1, "day"),
|
||||||
|
endDate: currentDate,
|
||||||
|
}),
|
||||||
|
timeResolution: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "Today",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).startOf("day"),
|
||||||
|
endDate: currentDate,
|
||||||
|
}),
|
||||||
|
timeResolution: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "Yesterday",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).subtract(1, "day").startOf("day"),
|
||||||
|
endDate: dayjs(currentDate).startOf("day").subtract(1, "second"),
|
||||||
|
}),
|
||||||
|
timeResolution: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "This week",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).startOf("week").add(1, "hour"),
|
||||||
|
endDate: currentDate,
|
||||||
|
}),
|
||||||
|
timeResolution: 360,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "Last week",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).subtract(1, "week").startOf("week").add(1, "hour"),
|
||||||
|
endDate: dayjs(currentDate).startOf("week").subtract(1, "second"),
|
||||||
|
}),
|
||||||
|
timeResolution: 1440,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "Previous 7 days",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).startOf("day").subtract(7, "day").add(1, "hour"),
|
||||||
|
endDate: currentDate.startOf("day"),
|
||||||
|
}),
|
||||||
|
timeResolution: 720,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "This month",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).startOf("month").add(1, "hour"),
|
||||||
|
endDate: currentDate,
|
||||||
|
}),
|
||||||
|
timeResolution: 1440,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "Last month",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).subtract(1, "month").startOf("month").add(1, "hour"),
|
||||||
|
endDate: dayjs(currentDate).startOf("month").subtract(1, "second"),
|
||||||
|
}),
|
||||||
|
timeResolution: 1440,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "Previous 30 days",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).startOf("day").subtract(30, "day").add(1, "hour"),
|
||||||
|
endDate: currentDate.startOf("day"),
|
||||||
|
}),
|
||||||
|
timeResolution: 1440,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "This quarter",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).startOf("quarter").add(1, "hour"),
|
||||||
|
endDate: currentDate,
|
||||||
|
}),
|
||||||
|
timeResolution: 1440,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "Last quarter",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).subtract(1, "quarter").startOf("quarter").add(1, "hour"),
|
||||||
|
endDate: dayjs(currentDate).startOf("quarter").subtract(1, "second"),
|
||||||
|
}),
|
||||||
|
timeResolution: 1440,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "This year",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).startOf("year"),
|
||||||
|
endDate: currentDate,
|
||||||
|
}),
|
||||||
|
timeResolution: 20160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "Last year",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).subtract(1, "year").startOf("year"),
|
||||||
|
endDate: dayjs(currentDate).startOf("year").subtract(1, "second"),
|
||||||
|
}),
|
||||||
|
timeResolution: 20160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "Previous year",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).subtract(1, "year"),
|
||||||
|
endDate: dayjs(currentDate),
|
||||||
|
}),
|
||||||
|
timeResolution: 40320,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getLabel: () => "Previous two years",
|
||||||
|
getDates: (currentDate: dayjs.Dayjs) => ({
|
||||||
|
startDate: dayjs(currentDate).subtract(2, "year"),
|
||||||
|
endDate: currentDate,
|
||||||
|
}),
|
||||||
|
timeResolution: 40320,
|
||||||
|
},
|
||||||
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -524,6 +714,20 @@ const defaultRanges: Record<number, [string, number] | string> = {
|
|||||||
min-height: auto;
|
min-height: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.label__subtitle {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-dropdown {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-area {
|
.chart-area {
|
||||||
@@ -688,6 +892,7 @@ const defaultRanges: Record<number, [string, number] | string> = {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--gap-xs);
|
gap: var(--gap-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.percentage-bar {
|
.percentage-bar {
|
||||||
grid-area: bar;
|
grid-area: bar;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -696,6 +901,7 @@ const defaultRanges: Record<number, [string, number] | string> = {
|
|||||||
border: 1px solid var(--color-button-bg);
|
border: 1px solid var(--color-button-bg);
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
display: block;
|
display: block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import quarterOfYear from "dayjs/plugin/quarterOfYear";
|
||||||
|
|
||||||
|
dayjs.extend(quarterOfYear);
|
||||||
|
|
||||||
export default defineNuxtPlugin(() => {
|
export default defineNuxtPlugin(() => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -304,13 +304,10 @@ export const useFetchAllAnalytics = (
|
|||||||
projects,
|
projects,
|
||||||
selectedProjects,
|
selectedProjects,
|
||||||
personalRevenue = false,
|
personalRevenue = false,
|
||||||
|
startDate = ref(dayjs().subtract(30, "days")),
|
||||||
|
endDate = ref(dayjs()),
|
||||||
|
timeResolution = ref(1440),
|
||||||
) => {
|
) => {
|
||||||
const timeResolution = ref(1440); // 1 day
|
|
||||||
const timeRange = ref(43200); // 30 days
|
|
||||||
|
|
||||||
const startDate = ref(Date.now() - timeRange.value * 60 * 1000);
|
|
||||||
const endDate = ref(Date.now());
|
|
||||||
|
|
||||||
const downloadData = ref(null);
|
const downloadData = ref(null);
|
||||||
const viewData = ref(null);
|
const viewData = ref(null);
|
||||||
const revenueData = ref(null);
|
const revenueData = ref(null);
|
||||||
@@ -394,8 +391,8 @@ export const useFetchAllAnalytics = (
|
|||||||
[() => startDate.value, () => endDate.value, () => timeResolution.value, () => projects.value],
|
[() => startDate.value, () => endDate.value, () => timeResolution.value, () => projects.value],
|
||||||
async () => {
|
async () => {
|
||||||
const q = {
|
const q = {
|
||||||
start_date: dayjs(startDate.value).toISOString(),
|
start_date: startDate.value.toISOString(),
|
||||||
end_date: dayjs(endDate.value).toISOString(),
|
end_date: endDate.value.toISOString(),
|
||||||
resolution_minutes: timeResolution.value,
|
resolution_minutes: timeResolution.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -442,7 +439,6 @@ export const useFetchAllAnalytics = (
|
|||||||
return {
|
return {
|
||||||
// Configuration
|
// Configuration
|
||||||
timeResolution,
|
timeResolution,
|
||||||
timeRange,
|
|
||||||
|
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
|
|||||||
Reference in New Issue
Block a user