feat: add month-based ranges to captain overview
This commit is contained in:
+79
@@ -0,0 +1,79 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useToggle } from '@vueuse/core';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
|
||||
|
||||
const modelValue = defineModel({ type: String, default: '30' });
|
||||
|
||||
const { t } = useI18n();
|
||||
const [showDropdown, toggleDropdown] = useToggle();
|
||||
|
||||
const DAY_RANGES = ['7', '30', '90'];
|
||||
|
||||
const decorate = item => ({
|
||||
...item,
|
||||
action: 'select',
|
||||
isSelected: item.value === modelValue.value,
|
||||
});
|
||||
|
||||
const menuSections = computed(() => {
|
||||
const dayItems = DAY_RANGES.map(value =>
|
||||
decorate({
|
||||
value,
|
||||
label: t('CAPTAIN.OVERVIEW.RANGES.LAST_DAYS', { count: value }),
|
||||
})
|
||||
);
|
||||
const monthItems = [
|
||||
decorate({
|
||||
value: 'this_month',
|
||||
label: t('CAPTAIN.OVERVIEW.RANGES.THIS_MONTH'),
|
||||
}),
|
||||
decorate({
|
||||
value: 'last_month',
|
||||
label: t('CAPTAIN.OVERVIEW.RANGES.LAST_MONTH'),
|
||||
}),
|
||||
];
|
||||
return [{ items: dayItems }, { items: monthItems }];
|
||||
});
|
||||
|
||||
const menuItems = computed(() =>
|
||||
menuSections.value.flatMap(section => section.items)
|
||||
);
|
||||
|
||||
const selectedLabel = computed(
|
||||
() => menuItems.value.find(item => item.isSelected)?.label || ''
|
||||
);
|
||||
|
||||
const handleAction = ({ value }) => {
|
||||
toggleDropdown(false);
|
||||
modelValue.value = value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-on-click-outside="() => toggleDropdown(false)"
|
||||
class="relative flex items-center group"
|
||||
>
|
||||
<Button
|
||||
sm
|
||||
slate
|
||||
faded
|
||||
trailing-icon
|
||||
icon="i-lucide-chevron-down"
|
||||
:label="selectedLabel"
|
||||
class="rounded-md group-hover:bg-n-alpha-2"
|
||||
@click="toggleDropdown()"
|
||||
/>
|
||||
<DropdownMenu
|
||||
v-if="showDropdown"
|
||||
:menu-items="menuItems"
|
||||
:menu-sections="menuSections"
|
||||
class="mt-1 ltr:right-0 rtl:left-0 top-full"
|
||||
@action="handleAction($event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -404,7 +404,9 @@
|
||||
"DISMISS": "Dismiss"
|
||||
},
|
||||
"RANGES": {
|
||||
"DAYS": "{count}d"
|
||||
"LAST_DAYS": "Last {count} days",
|
||||
"THIS_MONTH": "This month",
|
||||
"LAST_MONTH": "Last month"
|
||||
},
|
||||
"METRICS": {
|
||||
"HANDLED": {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import CaptainAssistant from 'dashboard/api/captain/assistant';
|
||||
|
||||
import PageLayout from 'dashboard/components-next/captain/PageLayout.vue';
|
||||
import RangeSelector from 'dashboard/components-next/captain/pageComponents/overview/RangeSelector.vue';
|
||||
import WelcomeCard from 'dashboard/components-next/captain/pageComponents/overview/WelcomeCard.vue';
|
||||
import MetricCard from 'dashboard/components-next/captain/pageComponents/overview/MetricCard.vue';
|
||||
import KnowledgeCard from 'dashboard/components-next/captain/pageComponents/overview/KnowledgeCard.vue';
|
||||
@@ -17,8 +18,7 @@ import InboxBanner from 'dashboard/components-next/captain/pageComponents/overvi
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
const ranges = ['7', '30', '90'];
|
||||
const selectedRange = ref('30');
|
||||
const selectedRange = ref('this_month');
|
||||
|
||||
const assistantId = computed(() => route.params.assistantId);
|
||||
const stats = ref(null);
|
||||
@@ -105,22 +105,7 @@ const metrics = computed(() => [
|
||||
:feature-flag="FEATURE_FLAGS.CAPTAIN"
|
||||
>
|
||||
<template #headerActions>
|
||||
<div class="flex items-center gap-1 p-1 rounded-lg bg-n-alpha-1 shrink-0">
|
||||
<button
|
||||
v-for="range in ranges"
|
||||
:key="range"
|
||||
type="button"
|
||||
class="px-3 py-1 text-sm font-medium rounded-md transition-colors"
|
||||
:class="
|
||||
selectedRange === range
|
||||
? 'bg-n-solid-active text-n-slate-12 shadow-sm'
|
||||
: 'text-n-slate-11 hover:text-n-slate-12'
|
||||
"
|
||||
@click="selectedRange = range"
|
||||
>
|
||||
{{ $t('CAPTAIN.OVERVIEW.RANGES.DAYS', { count: range }) }}
|
||||
</button>
|
||||
</div>
|
||||
<RangeSelector v-model="selectedRange" />
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="flex flex-col gap-6">
|
||||
@@ -147,7 +132,7 @@ const metrics = computed(() => [
|
||||
<ResponseQualityCard />
|
||||
</div>
|
||||
|
||||
<CreditUsageCard :range="selectedRange" />
|
||||
<CreditUsageCard />
|
||||
|
||||
<QuickLinks />
|
||||
</div>
|
||||
|
||||
@@ -5,14 +5,17 @@
|
||||
class Captain::AssistantStatsBuilder
|
||||
RESOLVED_EVENT_NAMES = %w[conversation_captain_inference_resolved conversation_bot_resolved].freeze
|
||||
HANDOFF_EVENT_NAMES = %w[conversation_captain_inference_handoff conversation_bot_handoff].freeze
|
||||
DEFAULT_RANGE_DAYS = 30
|
||||
DEFAULT_RANGE = '30'.freeze
|
||||
|
||||
attr_reader :assistant, :account, :range_days
|
||||
attr_reader :assistant, :account, :range
|
||||
|
||||
def initialize(assistant, range_days = DEFAULT_RANGE_DAYS)
|
||||
# `range` is either a day count ('7', '30', '90') or a named period
|
||||
# ('this_month', 'last_month'). The previous window mirrors the current one:
|
||||
# the preceding N days for day ranges, or the preceding month for month ranges.
|
||||
def initialize(assistant, range = DEFAULT_RANGE)
|
||||
@assistant = assistant
|
||||
@account = assistant.account
|
||||
@range_days = range_days.to_i.positive? ? range_days.to_i : DEFAULT_RANGE_DAYS
|
||||
@range = range.presence || DEFAULT_RANGE
|
||||
end
|
||||
|
||||
def metrics
|
||||
@@ -33,11 +36,37 @@ class Captain::AssistantStatsBuilder
|
||||
private
|
||||
|
||||
def current_range
|
||||
@current_range ||= (range_days.days.ago)..Time.current
|
||||
resolved_ranges[:current]
|
||||
end
|
||||
|
||||
def previous_range
|
||||
@previous_range ||= ((2 * range_days).days.ago)..range_days.days.ago
|
||||
resolved_ranges[:previous]
|
||||
end
|
||||
|
||||
def resolved_ranges
|
||||
@resolved_ranges ||= case range.to_s
|
||||
when 'this_month' then this_month_ranges
|
||||
when 'last_month' then last_month_ranges
|
||||
else day_ranges
|
||||
end
|
||||
end
|
||||
|
||||
def this_month_ranges
|
||||
start = Time.current.beginning_of_month
|
||||
elapsed = Time.current - start
|
||||
previous_start = start - 1.month
|
||||
{ current: start..Time.current, previous: previous_start..(previous_start + elapsed) }
|
||||
end
|
||||
|
||||
def last_month_ranges
|
||||
start = 1.month.ago.beginning_of_month
|
||||
previous_start = start - 1.month
|
||||
{ current: start..start.end_of_month, previous: previous_start..previous_start.end_of_month }
|
||||
end
|
||||
|
||||
def day_ranges
|
||||
days = range.to_i.positive? ? range.to_i : 30
|
||||
{ current: days.days.ago..Time.current, previous: (2 * days).days.ago..days.days.ago }
|
||||
end
|
||||
|
||||
# Raw metric values for a single window.
|
||||
|
||||
Reference in New Issue
Block a user