Skip to content

Insights ​

Overview ​

Insights surfaces in-app usage graphs for workspace members, pulling content-consumption data from the Laravel backend rather than from Amplitude. There are two surfaces:

  1. Dashboard Insights — a "Last 7 Days" bar chart widget on the workspace dashboard showing viewed, added, downloaded, and shared asset counts. Rendered by InsightsTabGraph with tab navigation across the four metrics.
  2. Asset / Folder Detail Insights — the same chart embedded in the asset detail panel as a tab, scoped to a single asset. Rendered by InsightsGraph, which shows all metrics as separate bar cards stacked vertically.

Both chart surfaces share the same Chart.js configuration, bar plugin, and weekday-label logic from the useInsightsChart composable. Insights data is not the same as Amplitude event tracking or portal analytics — see the comparison table below.

Architecture ​

Chart.js Bar components (from vue-chartjs) are used for rendering. Both InsightsGraph and InsightsTabGraph receive pre-built bar_data objects; they do not call APIs themselves. Data loading is handled upstream by page-level composables (useDashboard via useDashboardQuery, useAssetDetail for asset-scoped data).

The useInsightsChart composable is the single source of truth for all chart configuration. It computes real weekday initials for the trailing 7-day window using dayjs, defines the zero-bar-plugin that draws a 2px baseline under every bar, and exports buildInsightBarData() for constructing the two-dataset structure (bar + hover-dot scatter).

Date range filtering (start date, end date) and display state are managed by useAnalyticStore, a Pinia Options store. Store actions are the only way to update state — components call setter actions rather than writing directly to store properties.

File Structure ​

Store Files ​

  • app/stores/analytics.ts — useAnalyticStore Pinia Options store
    • Date range state: dStartDate, dEndDate, lastDate, activeDate
    • Transaction state: InsightsTransaction, InsightsTransactionShare
    • UI modal flags: isMoveModal, isCopyModal, isMoveFolderModal, isNewFolderModal
    • Misc state: status, statusList, getEmail, userAgentAndLocation
    • All 12 setter actions (one per state field)

Composable Files ​

  • app/composables/core/common/useInsightsChart.ts — chart configuration composable
    • insightsDayInitials — weekday letter array (S/M/T/W/T/F/S) for the actual trailing 7 days
    • insightBarPlugin — Chart.js plugin that draws a 2px baseline under each bar (purple for zero-day, bar color for non-zero)
    • insightsBarOptions — full Chart.js options object (tooltips, scales, hover, animation)
    • buildInsightBarData(labels, values, cardLabel) — builds two-dataset bar+scatter structure

Component Files ​

  • app/components/insights/InsightsTabGraph.vue — tabbed insights chart

    • v-tabs + v-window navigation across insight types
    • Tab order enforced: viewed → added → downloaded → shared (with remaining metrics appended)
    • Plan-gated metrics show as disabled tabs with an upgrade tooltip (code: 402)
    • activeTab and chartKey are reactive; chartKey increments on tab switch to force re-render
    • Used on the DAM dashboard
  • app/components/insights/InsightsGraph.vue — stacked insights bar chart

    • Renders each insight metric as a separate v-card with a Bar chart
    • No tab navigation — all metrics shown simultaneously
    • Used in asset / folder detail panels

Page / Query Files ​

  • app/composables/queries/useDashboardQuery.ts — TanStack Query for dashboard data

    • insightsQuery — fetches weekly insights for the workspace dashboard
    • Returns weeklyInsightLoading when isPending || isFetching
  • app/composables/core/pages/useDashboard.ts — dashboard page composable

    • weeklyInsightLoading — derived from insightsQuery.isPending || insightsQuery.isFetching
    • insightsQuery.data feeds InsightsTabGraph as weeklyInsightList

Style Files ​

  • app/assets/scss/pages/_insights.scss — insights-specific styles
    • .weekly-insights, .weekly-insights-list, .weekly-insights-chart-skeleton
    • Chart skeleton loaders, tab layout

InsightsTabGraph ​

File: app/components/insights/InsightsTabGraph.vue

Tabbed bar chart for the dashboard. Handles plan-gated metric tabs, enforces metric display order, and re-renders charts on tab switch via a reactive chartKey.

Props ​

typescript
interface Props {
  mainClass?: string                              // root v-card CSS class (default: 'boxview')
  heading?: string                                // card title (default: 'Last 7 Days')
  dataLoading?: boolean                           // shows skeleton when true
  weeklyInsightList?: Record<string, InsightItem> // keyed insight data from backend
  barOptions?: Record<string, unknown>            // Chart.js options override
  barPlugin?: Plugin<'bar'>[]                     // Chart.js plugins array
}

Internal State ​

typescript
const activeTab = ref(0)     // current v-tabs selection
const chartKey = ref(0)      // incremented on tab change to force chart re-render

// Enforced tab order — remaining keys appended after these four
const INSIGHT_ORDER = ['viewed', 'added', 'downloaded', 'shared']

Usage Example ​

vue
<template>
  <InsightsTabGraph
    heading="Last 7 Days"
    :data-loading="weeklyInsightLoading"
    :weekly-insight-list="insightsData"
    :bar-options="insightsBarOptions"
    :bar-plugin="[insightBarPlugin]"
  />
</template>

<script setup lang="ts">
const { insightsBarOptions, insightBarPlugin } = useInsightsChart()

const { insightsQuery, weeklyInsightLoading } = useDashboardQuery()
const insightsData = computed(() => insightsQuery.data.value ?? {})
</script>

InsightsGraph ​

File: app/components/insights/InsightsGraph.vue

Stacked chart variant — renders all insight metrics simultaneously as separate bar cards. Used in asset and folder detail panels.

Props ​

typescript
interface Props {
  mainClass?: string                              // additional CSS classes
  heading?: string                                // section heading (default: 'Last 7 Days')
  dataLoading?: boolean                           // shows skeleton loader when true
  weeklyInsightList?: Record<string, InsightItem> // keyed insight data
  barOptions?: Record<string, unknown>            // Chart.js options
  barPlugin?: Plugin<'bar'>[]                     // Chart.js plugins
}

Usage Example ​

vue
<template>
  <InsightsGraph
    heading="Asset Insights"
    :data-loading="assetInsightsLoading"
    :weekly-insight-list="assetInsights"
    :bar-options="insightsBarOptions"
    :bar-plugin="[insightBarPlugin]"
  />
</template>

<script setup lang="ts">
const { insightsBarOptions, insightBarPlugin } = useInsightsChart()
const assetInsightsLoading = ref(false)
const assetInsights = ref({})
</script>

useInsightsChart ​

File: app/composables/core/common/useInsightsChart.ts

Single source of truth for bar chart configuration used by both dashboard and asset-detail insight charts.

Returned Values ​

typescript
{
  insightsDayInitials: string[]
  // Weekday single letters (S/M/T/W/T/F/S) for the actual last 7 calendar days, oldest → today

  insightBarPlugin: { id: 'zero-bar-plugin', beforeDraw(chart): void }
  // Chart.js plugin: draws 2px baseline under each bar
  // Zero-value bars: light lavender (#E9E9FB); non-zero bars: bar color (#A8A8F0)

  insightsBarOptions: Record<string, unknown>
  // Full Chart.js options: no legend, custom external tooltip, hidden Y axis,
  // weekday-letter X-axis ticks, hover mode 'index'

  buildInsightBarData: (labels: string[], values: number[], cardLabel: string) => ChartData
  // Builds bar+scatter two-dataset structure
  // Bar color: #A8A8F0 (purple), hover: #075850 (dark green)
  // Scatter: hover-dot at points where value ≤ 20% of max (including zero)
}

Usage Example ​

vue
<script setup lang="ts">
const { insightsBarOptions, insightBarPlugin, buildInsightBarData, insightsDayInitials } = useInsightsChart()

// Build chart data from API response
const chartData = buildInsightBarData(
  insightsDayInitials,   // ['S', 'M', 'T', 'W', 'T', 'F', 'S']
  [12, 0, 4, 7, 0, 23, 8],  // daily counts, oldest → today
  'viewed'
)

// Pass to Bar component
// <Bar :chart-data="chartData" :options="insightsBarOptions" :plugins="[insightBarPlugin]" />
</script>

useAnalyticStore ​

File: app/stores/analytics.ts

Pinia Options store for insights date-range and UI state.

State ​

typescript
interface AnalyticsState {
  lastDate: string           // rolling window size in days (default: '29')
  status: string             // current status filter
  dStartDate: string         // start date 'YYYY-MM-DD' (default: today)
  dEndDate: string           // end date 'YYYY-MM-DD' (default: today)
  activeDate: string         // currently highlighted date 'YYYY-MM-DD'
  statusList: unknown[]      // list of available status values
  getEmail: string           // email filter
  isMoveModal: boolean       // move asset dialog open
  isCopyModal: boolean       // copy asset dialog open
  isMoveFolderModal: boolean // move folder dialog open
  isNewFolderModal: boolean  // new folder dialog open
  InsightsTransaction: Record<string, unknown>      // per-asset insight transaction data
  InsightsTransactionShare: Record<string, unknown> // sharing insight transaction data
  userAgentAndLocation: Record<string, unknown>     // browser/location data for support
}

Actions ​

typescript
setLastDate(payload: string): void        // update rolling window
setStartDate(payload: string): void       // update dStartDate
setEndDate(payload: string): void         // update dEndDate
setStatus(payload: string): void          // update status filter
setStatusList(payload: unknown[]): void   // update status options
setActiveDate(payload: string): void      // update highlighted date
setEmail(payload: string): void           // update email filter
setMoveModal(payload: boolean): void      // toggle move dialog
setCopyModal(payload: boolean): void      // toggle copy dialog
setMoveFolderModal(payload: boolean): void   // toggle folder move dialog
setNewFolderModal(payload: boolean): void    // toggle new folder dialog
setUserAgentAndLocation(payload: UserAgentAndLocation | null): void

Usage Example ​

vue
<script setup lang="ts">
const analyticStore = useAnalyticStore()

// Update date range for insights query
analyticStore.setStartDate('2025-07-01')
analyticStore.setEndDate('2025-07-31')
analyticStore.setLastDate('30')

// Read state
const dateRange = computed(() => ({
  from: analyticStore.dStartDate,
  to: analyticStore.dEndDate,
}))
</script>

Workflows ​

Dashboard Insights Load ​

useDashboard composable mounts
  → useDashboardQuery() returns insightsQuery
  → insightsQuery enabled when workspaceId resolved
  → fetches weekly insight data from Laravel backend
  → weeklyInsightLoading = isPending || isFetching
  ↓
InsightsTabGraph receives weeklyInsightList
  → orderedInsightList computed: viewed → added → downloaded → shared
  → plan-gated metrics (code: 402) rendered as disabled tabs with upgrade tooltip
  → v-tabs → v-window renders Bar chart per active tab
  → useInsightsChart.insightsBarOptions applied
  → zero-bar-plugin draws baselines
  → external tooltip: "N assets viewed on Mon"

Asset Insights Load ​

AssetDetailPanel opens asset
  → useAssetDetail composable fetches asset data
  → insight data returned alongside asset detail
  ↓
InsightsGraph rendered in Insights tab
  → all metrics rendered as stacked v-card bar charts
  → same chart config from useInsightsChart
  → no tab navigation — all metrics visible simultaneously

Chart Data Structure ​

The buildInsightBarData function returns a two-dataset structure:

typescript
{
  labels: string[],         // weekday initials: ['S', 'M', 'T', 'W', 'T', 'F', 'S']
  datasets: [
    {
      label: 'Graph Data',
      type: 'bar',
      data: number[],           // daily counts
      backgroundColor: '#A8A8F0',   // lavender purple
      hoverBackgroundColor: '#075850', // dark green on hover
      borderWidth: 0,
      card_label: 'viewed',     // used by tooltip callback
    },
    {
      type: 'scatter',
      label: 'Points',
      data: [{ x: 0, y: 12, show: false }, ...],
      pointHoverRadius: (ctx) => pointData?.show ? 5 : 0,
      // hover dot appears only for bars ≤ 20% of max value (including zero)
    }
  ]
}

Data Source Comparison ​

SurfaceData sourceScopeVisible to
Insights (dashboard, asset detail)Laravel backend countersWorkspace + individual assetsWorkspace members (any role)
Portal AnalyticsLaravel backendExternal portal visitor activityWorkspace members on portal detail
AmplitudeClient-side event trackingUI interactionsInternal team only (Amplitude dashboard)

Insights counts authenticated workspace-member views and downloads. Portal Analytics counts external visitor activity. Amplitude tracks product interaction events for internal analysis.

Component Integration ​

Dashboard (app/pages/[workspace_id]/dam/dashboard/index.vue) ​

All insight state comes from useDashboard(). The composable builds weeklyInsightList by processing the raw insightsQuery data through buildInsightBarData() from useInsightsChart(). The page receives InsightBarPlugin as a single object and casts it to the Chart.js plugin array shape that InsightsTabGraph expects.

vue
<template>
  <InsightsTabGraph
    main-class="boxview weekly-insights h-auto mt-4 mt-sm-6 mt-lg-0 mb-4 mb-sm-6"
    heading="Last 7 Days"
    :data-loading="weeklyInsightLoading"
    :weekly-insight-list="weeklyInsightList"
    :bar-options="InsightsBarOptions"
    :bar-plugin="insightBarPlugins"
  />
</template>

<script setup lang="ts">
const {
  weeklyInsightLoading,
  weeklyInsightList,
  InsightsBarOptions,
  InsightBarPlugin,
} = useDashboard()

const insightBarPlugins = [InsightBarPlugin] as unknown as import('chart.js').Plugin<'bar'>[]
</script>

Asset Detail (app/pages/[workspace_id]/dam/files/[_id]/index.vue) ​

The Insights tab in the asset detail panel renders InsightsGraph (stacked variant, no tab navigation). State comes from useAssetDetail(). The page casts weeklyInsightList to the InsightItem-keyed shape that InsightsGraph expects, and applies the same plugin cast as the dashboard.

vue
<template>
  <InsightsGraph
    heading="Last 7 Days"
    :data-loading="weeklyInsightLoading"
    :weekly-insight-list="weeklyInsightListForChart"
    :bar-options="InsightsBarOptions"
    :bar-plugin="insightBarPlugins"
  />
</template>

<script setup lang="ts">
const {
  weeklyInsightLoading,
  weeklyInsightList,
  InsightsBarOptions,
  InsightBarPlugin,
} = useAssetDetail()

const weeklyInsightListForChart = computed(
  () => weeklyInsightList.value as Record<string, { card_label?: string; bar_data?: unknown; code?: number }>
)
const insightBarPlugins = [InsightBarPlugin] as unknown as import('chart.js').Plugin<'bar'>[]
</script>