diff --git a/app/javascript/dashboard/api/captain/assistant.js b/app/javascript/dashboard/api/captain/assistant.js index 1fc17798d..5af6110ab 100644 --- a/app/javascript/dashboard/api/captain/assistant.js +++ b/app/javascript/dashboard/api/captain/assistant.js @@ -26,15 +26,18 @@ class CaptainAssistant extends ApiClient { }); } - getStats({ assistantId, range }) { - return axios.get(`${this.url}/${assistantId}/stats`, { + getStats({ assistantId, range, signal }) { + const requestConfig = { params: { range, timezone_offset: getTimezoneOffset() }, - }); + }; + if (signal) requestConfig.signal = signal; + + return axios.get(`${this.url}/${assistantId}/stats`, requestConfig); } - getSummary({ assistantId, range }) { + getSummary({ assistantId, range, stats }) { return axios.get(`${this.url}/${assistantId}/summary`, { - params: { range, timezone_offset: getTimezoneOffset() }, + params: { range, timezone_offset: getTimezoneOffset(), stats }, }); } diff --git a/app/javascript/dashboard/components-next/captain/pageComponents/overview/MetricCard.vue b/app/javascript/dashboard/components-next/captain/pageComponents/overview/MetricCard.vue index cf66a0a2f..9a68b71ce 100644 --- a/app/javascript/dashboard/components-next/captain/pageComponents/overview/MetricCard.vue +++ b/app/javascript/dashboard/components-next/captain/pageComponents/overview/MetricCard.vue @@ -9,6 +9,7 @@ const props = defineProps({ // null = neutral, true = good direction, false = bad direction trendGood: { type: Boolean, default: null }, clickable: { type: Boolean, default: false }, + loading: { type: Boolean, default: false }, }); const emit = defineEmits(['click']); @@ -45,7 +46,11 @@ const onActivate = () => { class="transition-opacity opacity-0 cursor-help i-lucide-info size-3.5 text-n-slate-10 group-hover:opacity-100" /> -
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/captain/pageComponents/overview/WelcomeCard.vue b/app/javascript/dashboard/components-next/captain/pageComponents/overview/WelcomeCard.vue index df336a7e4..5e868956f 100644 --- a/app/javascript/dashboard/components-next/captain/pageComponents/overview/WelcomeCard.vue +++ b/app/javascript/dashboard/components-next/captain/pageComponents/overview/WelcomeCard.vue @@ -9,6 +9,10 @@ const props = defineProps({ type: String, default: '30', }, + stats: { + type: Object, + default: null, + }, }); const route = useRoute(); @@ -20,22 +24,41 @@ const assistantId = computed(() => route.params.assistantId); const welcomeMarkdown = ref(''); const isLoading = ref(false); +// Increments on every fetch so a slow response for a superseded +// range/stats/assistant can't overwrite the latest request's state. +let fetchToken = 0; + const fetchSummary = async () => { + fetchToken += 1; + const token = fetchToken; + + if (!props.stats) { + welcomeMarkdown.value = ''; + isLoading.value = false; + return; + } + isLoading.value = true; + let message = ''; try { const { data } = await CaptainAssistant.getSummary({ assistantId: assistantId.value, range: props.range, + stats: props.stats, }); - welcomeMarkdown.value = data.message ?? ''; + message = data.message ?? ''; } catch { - welcomeMarkdown.value = ''; - } finally { - isLoading.value = false; + message = ''; } + + if (token !== fetchToken) return; + welcomeMarkdown.value = message; + isLoading.value = false; }; -watch([() => props.range, assistantId], fetchSummary, { immediate: true }); +watch([() => props.range, () => props.stats, assistantId], fetchSummary, { + immediate: true, +}); // Render through the shared markdown formatter (html disabled, so it is safe) // used everywhere else for Captain output, instead of a bespoke parser. It diff --git a/app/javascript/dashboard/routes/dashboard/captain/assistants/overview/Index.vue b/app/javascript/dashboard/routes/dashboard/captain/assistants/overview/Index.vue index 29411e56d..ba31bc05d 100644 --- a/app/javascript/dashboard/routes/dashboard/captain/assistants/overview/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/captain/assistants/overview/Index.vue @@ -1,5 +1,5 @@