Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 78 additions & 63 deletions app/components/BreakdownComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<div class="spacing-25"/>
<div class="text-h6 mb-1">Number of {{ breakdownDisplayNamePlural }}</div>
<div class="text-caption">
Over the last 28 days
{{ dateRangeDescription }}
</div>
<p class="text-h4">{{ numberOfBreakdowns }}</p>
</div>
Expand Down Expand Up @@ -79,7 +79,7 @@
</template>

<script lang="ts">
import { defineComponent, ref, toRef } from 'vue';
import { defineComponent, ref, toRef, watch } from 'vue';
import type { Metrics } from '@/model/Metrics';
import { Breakdown } from '@/model/Breakdown';
import { Pie } from 'vue-chartjs'
Expand Down Expand Up @@ -122,6 +122,10 @@ export default defineComponent({
breakdownKey: {
type: String,
required: true
},
dateRangeDescription: {
type: String,
default: 'Over the last 28 days'
}
},
setup(props) {
Expand Down Expand Up @@ -157,76 +161,87 @@ export default defineComponent({
'#7CFC00' // Lawn Green
]);

const data = toRef(props, 'metrics').value;
// Function to process breakdown data
const processBreakdownData = (data: Metrics[]) => {
// Reset the breakdown list
breakdownList.value = [];

// Process the breakdown separately
data.forEach((m: Metrics) => m.breakdown.forEach(breakdownData =>
{
const breakdownName = breakdownData[props.breakdownKey as keyof typeof breakdownData] as string;
let breakdown = breakdownList.value.find(b => b.name === breakdownName);

// Process the breakdown separately
data.forEach((m: Metrics) => m.breakdown.forEach(breakdownData =>
{
const breakdownName = breakdownData[props.breakdownKey as keyof typeof breakdownData] as string;
let breakdown = breakdownList.value.find(b => b.name === breakdownName);
if (!breakdown) {
// Create a new breakdown object if it does not exist
breakdown = new Breakdown({
name: breakdownName,
acceptedPrompts: breakdownData.acceptances_count,
suggestedPrompts: breakdownData.suggestions_count,
suggestedLinesOfCode: breakdownData.lines_suggested,
acceptedLinesOfCode: breakdownData.lines_accepted,
});
breakdownList.value.push(breakdown);
} else {
// Update the existing breakdown object
breakdown.acceptedPrompts += breakdownData.acceptances_count;
breakdown.suggestedPrompts += breakdownData.suggestions_count;
breakdown.suggestedLinesOfCode += breakdownData.lines_suggested;
breakdown.acceptedLinesOfCode += breakdownData.lines_accepted;
}
// Recalculate the acceptance rates
breakdown.acceptanceRateByCount = breakdown.suggestedPrompts !== 0 ? (breakdown.acceptedPrompts / breakdown.suggestedPrompts) * 100 : 0;
breakdown.acceptanceRateByLines = breakdown.suggestedLinesOfCode !== 0 ? (breakdown.acceptedLinesOfCode / breakdown.suggestedLinesOfCode) * 100 : 0;

if (!breakdown) {
// Create a new breakdown object if it does not exist
breakdown = new Breakdown({
name: breakdownName,
acceptedPrompts: breakdownData.acceptances_count,
suggestedPrompts: breakdownData.suggestions_count,
suggestedLinesOfCode: breakdownData.lines_suggested,
acceptedLinesOfCode: breakdownData.lines_accepted,
});
breakdownList.value.push(breakdown);
} else {
// Update the existing breakdown object
breakdown.acceptedPrompts += breakdownData.acceptances_count;
breakdown.suggestedPrompts += breakdownData.suggestions_count;
breakdown.suggestedLinesOfCode += breakdownData.lines_suggested;
breakdown.acceptedLinesOfCode += breakdownData.lines_accepted;
}
// Recalculate the acceptance rates
breakdown.acceptanceRateByCount = breakdown.suggestedPrompts !== 0 ? (breakdown.acceptedPrompts / breakdown.suggestedPrompts) * 100 : 0;
breakdown.acceptanceRateByLines = breakdown.suggestedLinesOfCode !== 0 ? (breakdown.acceptedLinesOfCode / breakdown.suggestedLinesOfCode) * 100 : 0;
// Log each breakdown for debugging
// console.log('Breakdown:', breakdown);
}));

// Log each breakdown for debugging
// console.log('Breakdown:', breakdown);
}));
//Sort breakdowns map by accepted prompts
breakdownList.value.sort((a, b) => b.acceptedPrompts - a.acceptedPrompts);

//Sort breakdowns map by accepted prompts
breakdownList.value.sort((a, b) => b.acceptedPrompts - a.acceptedPrompts);
// Get the top 5 breakdowns by accepted prompts
const top5BreakdownsAcceptedPrompts = breakdownList.value.slice(0, 5);

breakdownsChartDataTop5AcceptedPrompts.value = {
labels: top5BreakdownsAcceptedPrompts.map(breakdown => breakdown.name),
datasets: [
{
data: top5BreakdownsAcceptedPrompts.map(breakdown => breakdown.acceptedPrompts),
backgroundColor: pieChartColors.value,
},
],
};

// Get the top 5 breakdowns by accepted prompts
const top5BreakdownsAcceptedPrompts = breakdownList.value.slice(0, 5);

breakdownsChartDataTop5AcceptedPrompts.value = {
labels: top5BreakdownsAcceptedPrompts.map(breakdown => breakdown.name),
datasets: [
{
data: top5BreakdownsAcceptedPrompts.map(breakdown => breakdown.acceptedPrompts),
backgroundColor: pieChartColors.value,
},
],
};
breakdownsChartDataTop5AcceptedPromptsByLines.value = {
labels: top5BreakdownsAcceptedPrompts.map(breakdown => breakdown.name),
datasets: [
{
data: top5BreakdownsAcceptedPrompts.map(breakdown => breakdown.acceptanceRateByLines.toFixed(2)),
backgroundColor: pieChartColors.value,
},
],
};

breakdownsChartDataTop5AcceptedPromptsByLines.value = {
labels: top5BreakdownsAcceptedPrompts.map(breakdown => breakdown.name),
datasets: [
{
data: top5BreakdownsAcceptedPrompts.map(breakdown => breakdown.acceptanceRateByLines.toFixed(2)),
backgroundColor: pieChartColors.value,
},
],
};
breakdownsChartDataTop5AcceptedPromptsByCounts.value = {
labels: top5BreakdownsAcceptedPrompts.map(breakdown => breakdown.name),
datasets: [
{
data: top5BreakdownsAcceptedPrompts.map(breakdown => breakdown.acceptanceRateByCount.toFixed(2)),
backgroundColor: pieChartColors.value,
},
],
};

breakdownsChartDataTop5AcceptedPromptsByCounts.value = {
labels: top5BreakdownsAcceptedPrompts.map(breakdown => breakdown.name),
datasets: [
{
data: top5BreakdownsAcceptedPrompts.map(breakdown => breakdown.acceptanceRateByCount.toFixed(2)),
backgroundColor: pieChartColors.value,
},
],
numberOfBreakdowns.value = breakdownList.value.length;
};

numberOfBreakdowns.value = breakdownList.value.length;
// Watch for changes in metrics prop and re-process data
watch(() => props.metrics, (newMetrics) => {
if (newMetrics && Array.isArray(newMetrics)) {
processBreakdownData(newMetrics);
}
}, { immediate: true, deep: true });

return { chartOptions, breakdownList, numberOfBreakdowns,
breakdownsChartData, breakdownsChartDataTop5AcceptedPrompts, breakdownsChartDataTop5AcceptedPromptsByLines, breakdownsChartDataTop5AcceptedPromptsByCounts };
Expand Down
8 changes: 6 additions & 2 deletions app/components/CopilotChatViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<span class="text-caption" style="font-size: 10px !important;">This metric represents the total number of turns (interactions) with the Copilot over the past 28 days. A 'turn' includes both user inputs and Copilot's responses.</span>
</v-card>
</v-tooltip>
<div class="text-caption">Over the last 28 days</div>
<div class="text-caption">{{ dateRangeDescription }}</div>
<p class="text-h4">{{ cumulativeNumberTurns }}</p>
</div>
</v-card-item>
Expand All @@ -30,7 +30,7 @@
<span class="text-caption" style="font-size: 10px !important;">This metric shows the total number of lines of code suggested by Copilot that have been accepted by users over the past 28 days.</span>
</v-card>
</v-tooltip>
<div class="text-caption">Over the last 28 days</div>
<div class="text-caption">{{ dateRangeDescription }}</div>
<p class="text-h4">{{ cumulativeNumberAcceptances }}</p>
</div>
</v-card-item>
Expand Down Expand Up @@ -103,6 +103,10 @@ props: {
metrics: {
type: Object,
required: true
},
dateRangeDescription: {
type: String,
default: 'Over the last 28 days'
}
},
setup(props) {
Expand Down
156 changes: 156 additions & 0 deletions app/components/DateRangeSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<template>
<v-card class="pa-4 ma-4" elevation="2">
<v-card-title class="text-h6 pb-2">Date Range Filter</v-card-title>
<v-row align="end">
<v-col cols="12" sm="4">
<v-text-field
v-model="fromDate"
label="From Date"
type="date"
variant="outlined"
density="compact"
@update:model-value="updateDateRange"
/>
</v-col>
<v-col cols="12" sm="4">
<v-text-field
v-model="toDate"
label="To Date"
type="date"
variant="outlined"
density="compact"
@update:model-value="updateDateRange"
/>
</v-col>
<v-col cols="12" sm="4" class="d-flex align-center justify-start" style="padding-bottom: 35px;">
<v-btn
color="primary"
variant="outlined"
size="default"
class="mr-3"
@click="resetToDefault"
>
Last 28 Days
</v-btn>
<v-btn
color="primary"
size="default"
:loading="loading"
@click="applyDateRange"
>
Apply
</v-btn>
</v-col>
</v-row>
<v-card-text class="pt-2">
<span class="text-caption text-medium-emphasis">
{{ dateRangeText }}
</span>
</v-card-text>
</v-card>
</template>

<script setup lang="ts">
interface Props {
loading?: boolean
}

interface Emits {
(e: 'date-range-changed', value: { since?: string; until?: string; description: string }): void
}

withDefaults(defineProps<Props>(), {
loading: false
})

const emit = defineEmits<Emits>()

// Calculate default dates (last 28 days)
const today = new Date()
const defaultFromDate = new Date(today.getTime() - 27 * 24 * 60 * 60 * 1000) // 27 days ago to include today

const fromDate = ref(formatDate(defaultFromDate))
const toDate = ref(formatDate(today))

function formatDate(date: Date): string {
return date.toISOString().split('T')[0] || ''
}

function parseDate(dateString: string): Date {
return new Date(dateString + 'T00:00:00.000Z')
}

const dateRangeText = computed(() => {
if (!fromDate.value || !toDate.value) {
return 'Select date range'
}

const from = parseDate(fromDate.value)
const to = parseDate(toDate.value)
const diffDays = Math.ceil((to.getTime() - from.getTime()) / (1000 * 60 * 60 * 24)) + 1

if (diffDays === 1) {
return `For ${from.toLocaleDateString()}`
} else if (diffDays <= 28 && isLast28Days()) {
return 'Over the last 28 days'
} else {
return `From ${from.toLocaleDateString()} to ${to.toLocaleDateString()} (${diffDays} days)`
}
})

function isLast28Days(): boolean {
if (!fromDate.value || !toDate.value) return false

const today = new Date()
const expectedFromDate = new Date(today.getTime() - 27 * 24 * 60 * 60 * 1000)

const from = parseDate(fromDate.value)
const to = parseDate(toDate.value)

return (
from.toDateString() === expectedFromDate.toDateString() &&
to.toDateString() === today.toDateString()
)
}

function updateDateRange() {
// This function is called when dates change, but we don't auto-apply
// User needs to click Apply button
}

function resetToDefault() {
const today = new Date()
const defaultFrom = new Date(today.getTime() - 27 * 24 * 60 * 60 * 1000)

fromDate.value = formatDate(defaultFrom)
toDate.value = formatDate(today)
}

function applyDateRange() {
if (!fromDate.value || !toDate.value) {
return
}

const from = parseDate(fromDate.value)
const to = parseDate(toDate.value)

if (from > to) {
// Swap dates if from is after to
const temp = fromDate.value
fromDate.value = toDate.value
toDate.value = temp
}

// Emit the date range change
emit('date-range-changed', {
since: fromDate.value,
until: toDate.value,
description: dateRangeText.value
})
}

// Initialize with default range on mount
onMounted(() => {
applyDateRange()
})
</script>
Loading
Loading