Files
chatwoot/app/javascript/shared/components/charts/BarChart.vue
6a7ca9dd3b feat: Add report bar drilldown drawer (#14626)
## Description

Adds drilldown support for report bar charts powered by
`ReportContainer`. Clicking a non-zero report bar now opens a right-side
drawer with the conversations or messages that contributed to that
bucket, with each row linking to the underlying conversation and message
rows linking with `messageId`.

This includes a new `GET /api/v2/accounts/:account_id/reports/drilldown`
endpoint, backend drilldown builders/serializers, generic chart click
emission, local drawer state via `useReportDrilldown`, compact drilldown
cards, pagination, stale-response protection, and validation for
unsupported drilldown dimensions.

Fixes # CW-4497

https://linear.app/chatwoot/issue/CW-4497/drill-down-on-agent-conversations-report

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update

## How Has This Been Tested?

Ran the focused backend and frontend checks for the drilldown endpoint,
builder, chart click handling, drawer/card UI, API helper, and
stale-response handling.

Here are the screenshots on how it looks like:
<img width="1792" height="1199" alt="Screenshot 2026-06-02 at 11 32
11 PM"
src="https://github.com/user-attachments/assets/6bdb8832-b9df-4bf3-9a2a-beaefe203b6e"
/>
<img width="1791" height="1230" alt="Screenshot 2026-06-02 at 11 32
34 PM"
src="https://github.com/user-attachments/assets/36e92eb7-3208-4855-87f4-0c7f316df54d"
/>
<img width="1784" height="1235" alt="Screenshot 2026-06-02 at 11 32
46 PM"
src="https://github.com/user-attachments/assets/f7a53916-74f2-4622-9305-042e0ac9e877"
/>



## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2026-07-02 16:07:26 +05:30

115 lines
2.2 KiB
Vue

<script setup>
import { computed } from 'vue';
import { Bar } from 'vue-chartjs';
import {
Chart as ChartJS,
Title,
Tooltip,
BarElement,
CategoryScale,
LinearScale,
} from 'chart.js';
const props = defineProps({
collection: {
type: Object,
default: () => ({}),
},
chartOptions: {
type: Object,
default: () => ({}),
},
clickable: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['elementClick']);
ChartJS.register(Title, Tooltip, BarElement, CategoryScale, LinearScale);
const fontFamily =
'Inter,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
const defaultChartOptions = {
responsive: true,
maintainAspectRatio: false,
legend: {
display: false,
labels: {
fontFamily,
},
},
animation: {
duration: 0,
},
datasets: {
bar: {
barPercentage: 1.0,
},
},
scales: {
x: {
ticks: {
fontFamily: fontFamily,
},
grid: {
drawOnChartArea: false,
},
},
y: {
type: 'linear',
position: 'left',
ticks: {
fontFamily: fontFamily,
beginAtZero: true,
stepSize: 1,
},
grid: {
drawOnChartArea: false,
},
},
},
};
const handleClick = (event, elements, chart) => {
props.chartOptions.onClick?.(event, elements, chart);
if (!props.clickable || !elements.length) return;
const { datasetIndex, index } = elements[0];
const dataset = props.collection.datasets?.[datasetIndex] || {};
emit('elementClick', {
datasetIndex,
dataIndex: index,
dataset,
label: props.collection.labels?.[index],
value: dataset.data?.[index],
});
};
const handleHover = (event, elements, chart) => {
props.chartOptions.onHover?.(event, elements, chart);
if (!event?.native?.target) return;
event.native.target.style.cursor =
props.clickable && elements.length ? 'pointer' : 'default';
};
const options = computed(() => {
return {
...defaultChartOptions,
...props.chartOptions,
onClick: handleClick,
onHover: handleHover,
};
});
</script>
<template>
<Bar :data="collection" :options="options" />
</template>