feat(reports): add common monitor templates (#16011)

The empty Monitors page keeps its centered heading, create action, faded
preview, and compact template list. It now offers four familiar customer
conversation topics: missing order updates, feature requests, refund
requests, and cancellation intent. Selecting one prefills its name and
full condition. The preview uses example text without invented counts,
and the WhatsApp BSUID and self-hosted automation examples are removed.

Related to #15966.

## How to test

1. Open Reports → Monitors in an account with no monitors. Confirm the
existing centered empty-state layout and four compact template rows
appear.
2. Select Missing order updates or Feature requests. Confirm the
creation form contains the corresponding name and full condition.
3. Confirm Login problems, Competitor mentions, WhatsApp BSUID, and
self-hosted automation examples are absent, and the faded preview shows
no fabricated counts.
4. Sign in without administrator access. Confirm the templates are
disabled and the administrator help text appears.
This commit is contained in:
Sony Mathew
2026-09-25 12:58:28 -07:00
committed by GitHub
parent fcadd60492
commit 2cfe79ec1e
3 changed files with 78 additions and 40 deletions

View File

@@ -22,19 +22,27 @@
"EMPTY_TITLE": "Turn conversations into a trend you can follow",
"EMPTY_DESCRIPTION": "Describe a topic in plain English. A monitor finds matching conversations and keeps its chart updated as new customer messages arrive.",
"ADMIN_HELP": "Ask an administrator to create your first monitor.",
"EXAMPLES_TITLE": "Try an example",
"EXAMPLES_TITLE": "Start with a template",
"EXAMPLES": {
"REFUNDS": {
"NAME": "Refund conversations",
"CONDITION": "All conversations mentioning refunds"
"MISSING_ORDER_UPDATES": {
"NAME": "Missing order updates",
"CONDITION": "A customer says an order confirmation, shipping update, or delivery notice did not arrive or was sent for the wrong order. Exclude questions about parcel location when the customer received tracking information.",
"BENEFIT": "Track recurring delivery communication problems."
},
"BSUID": {
"NAME": "WhatsApp BSUID",
"CONDITION": "All conversations related to WhatsApp BSUID"
"FEATURE_REQUESTS": {
"NAME": "Feature requests",
"CONDITION": "A customer asks for a capability the product does not currently offer. Exclude questions about how to use a feature that already exists.",
"BENEFIT": "Keep a running count of unmet needs."
},
"AUTOMATIONS": {
"NAME": "Self-hosted automations",
"CONDITION": "All conversations related to automations from a self-hosted customer"
"REFUND_REQUESTS": {
"NAME": "Refund requests",
"CONDITION": "A customer asks for money back on a payment already made, including duplicate charges or refunds after cancellation. Exclude questions about the refund policy and requests for a discount.",
"BENEFIT": "Bring a consistent definition to refund requests."
},
"CANCELLATION_INTENT": {
"NAME": "Cancellation intent",
"CONDITION": "A customer says they intend to cancel, will not renew, or plan to stop using the product. Exclude requests to reduce seats or downgrade, and cancellations withdrawn in the same conversation.",
"BENEFIT": "Find customers who may need a conversation before they leave."
}
},
"MATCHING_CONVERSATIONS": "Matching conversations created",

View File

@@ -4,51 +4,63 @@ import { useI18n } from 'vue-i18n';
import { useAdmin } from 'dashboard/composables/useAdmin';
import Button from 'dashboard/components-next/button/Button.vue';
import Icon from 'dashboard/components-next/icon/Icon.vue';
import MonitorListItem from './MonitorListItem.vue';
const emit = defineEmits(['create']);
const PREVIEW_COUNTS = [128, 64, 37];
const { t } = useI18n();
const { isAdmin } = useAdmin();
const examples = computed(() => [
{
name: t('MONITORS.EXAMPLES.REFUNDS.NAME'),
condition: t('MONITORS.EXAMPLES.REFUNDS.CONDITION'),
name: t('MONITORS.EXAMPLES.MISSING_ORDER_UPDATES.NAME'),
condition: t('MONITORS.EXAMPLES.MISSING_ORDER_UPDATES.CONDITION'),
benefit: t('MONITORS.EXAMPLES.MISSING_ORDER_UPDATES.BENEFIT'),
},
{
name: t('MONITORS.EXAMPLES.BSUID.NAME'),
condition: t('MONITORS.EXAMPLES.BSUID.CONDITION'),
name: t('MONITORS.EXAMPLES.FEATURE_REQUESTS.NAME'),
condition: t('MONITORS.EXAMPLES.FEATURE_REQUESTS.CONDITION'),
benefit: t('MONITORS.EXAMPLES.FEATURE_REQUESTS.BENEFIT'),
},
{
name: t('MONITORS.EXAMPLES.AUTOMATIONS.NAME'),
condition: t('MONITORS.EXAMPLES.AUTOMATIONS.CONDITION'),
name: t('MONITORS.EXAMPLES.REFUND_REQUESTS.NAME'),
condition: t('MONITORS.EXAMPLES.REFUND_REQUESTS.CONDITION'),
benefit: t('MONITORS.EXAMPLES.REFUND_REQUESTS.BENEFIT'),
},
{
name: t('MONITORS.EXAMPLES.CANCELLATION_INTENT.NAME'),
condition: t('MONITORS.EXAMPLES.CANCELLATION_INTENT.CONDITION'),
benefit: t('MONITORS.EXAMPLES.CANCELLATION_INTENT.BENEFIT'),
},
]);
const previewMonitors = computed(() =>
examples.value.map((example, index) => ({
...example,
id: index + 1,
recent_count: PREVIEW_COUNTS[index],
paused_at: null,
}))
);
</script>
<template>
<section class="flex flex-col items-center gap-8 pb-12">
<div
inert
aria-hidden="true"
class="w-full select-none opacity-70 [mask-image:linear-gradient(to_bottom,black,transparent)]"
>
<div class="flex flex-col divide-y divide-n-weak border-t border-n-weak">
<MonitorListItem
v-for="monitor in previewMonitors"
:key="monitor.id"
:monitor="monitor"
/>
<div
v-for="example in examples.slice(0, 3)"
:key="example.name"
class="flex min-w-0 items-center gap-4 py-4"
>
<span
class="flex size-10 shrink-0 items-center justify-center rounded-xl outline outline-1 -outline-offset-1 outline-n-weak"
>
<Icon icon="i-lucide-monitor" class="size-4 text-n-slate-11" />
</span>
<span class="flex min-w-0 flex-col gap-0.5">
<span class="truncate text-heading-3 text-n-slate-12">{{
example.name
}}</span>
<span class="truncate text-body-main text-n-slate-11">{{
example.condition
}}</span>
</span>
</div>
</div>
</div>
<div class="flex flex-col items-center gap-3 text-center">
@@ -74,14 +86,22 @@ const previewMonitors = computed(() =>
</span>
<button
v-for="example in examples"
:key="example.condition"
:key="example.name"
type="button"
:disabled="!isAdmin"
class="flex w-full items-center justify-between gap-2 rounded-lg border border-n-weak bg-n-slate-2 px-3 py-2 text-start text-sm text-n-slate-11 transition-colors enabled:hover:bg-n-slate-3 enabled:hover:text-n-slate-12 disabled:cursor-not-allowed disabled:opacity-60"
@click="emit('create', example)"
@click="
emit('create', { name: example.name, condition: example.condition })
"
>
<span class="min-w-0 truncate">{{ example.condition }}</span>
<Icon icon="i-lucide-chevron-right" class="size-4 shrink-0" />
<span class="flex min-w-0 flex-col gap-0.5">
<span class="font-medium text-n-slate-12">{{ example.name }}</span>
<span class="truncate">{{ example.benefit }}</span>
</span>
<Icon
icon="i-lucide-chevron-right"
class="size-4 shrink-0 rtl:rotate-180"
/>
</button>
</div>
</section>

View File

@@ -12,22 +12,32 @@ vi.mock('vue-i18n', async importOriginal => ({
}));
describe('MonitorsEmptyState', () => {
it('lets an administrator create a monitor or start from an example', async () => {
it('lets an administrator create a monitor or start from a template', async () => {
state.isAdmin = ref(true);
const wrapper = shallowMount(MonitorsEmptyState);
wrapper.findComponent({ name: 'Button' }).vm.$emit('click');
await wrapper.findAll('button')[1].trigger('click');
await wrapper.findAll('button')[0].trigger('click');
expect(wrapper.emitted('create')).toEqual([
[],
[
{
name: 'MONITORS.EXAMPLES.BSUID.NAME',
condition: 'MONITORS.EXAMPLES.BSUID.CONDITION',
name: 'MONITORS.EXAMPLES.MISSING_ORDER_UPDATES.NAME',
condition: 'MONITORS.EXAMPLES.MISSING_ORDER_UPDATES.CONDITION',
},
],
]);
expect(wrapper.findAll('button')).toHaveLength(4);
expect(wrapper.text()).toContain('MONITORS.EMPTY_TITLE');
expect(wrapper.text()).toContain('MONITORS.EXAMPLES.FEATURE_REQUESTS.NAME');
expect(wrapper.text()).toContain(
'MONITORS.EXAMPLES.FEATURE_REQUESTS.BENEFIT'
);
expect(wrapper.text()).not.toContain('MONITORS.EXAMPLES.LOGIN_PROBLEMS');
expect(wrapper.text()).not.toContain(
'MONITORS.EXAMPLES.COMPETITOR_MENTIONS'
);
expect(wrapper.text()).not.toContain('MONITORS.ADMIN_HELP');
});