Skip to content

Commit 8917a8f

Browse files
committed
Refactor code and add CopilotChatViewer component
1 parent 0faa082 commit 8917a8f

File tree

4 files changed

+360
-35
lines changed

4 files changed

+360
-35
lines changed

src/components/CopilotChatViewer.vue

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<template>
2+
<div>
3+
</div>
4+
</template>
5+
6+
<script lang="ts">
7+
import { defineComponent, ref } from 'vue';
8+
import { getGitHubCopilotMetricsApi } from '../api/GitHubApi';
9+
import { Metrics } from '../model/MetricsData';
10+
11+
12+
export default defineComponent({
13+
name: 'CopilotChatViewer',
14+
setup() {
15+
const metrics = ref<Metrics[]>([]);
16+
17+
const apiError = ref<string>('');
18+
19+
getGitHubCopilotMetricsApi().then(data => {
20+
metrics.value = data;
21+
}).catch(error => {
22+
console.log(error);
23+
// Check the status code of the error response
24+
if (error.response && error.response.status) {
25+
switch (error.response.status) {
26+
case 401:
27+
apiError.value = '401 Unauthorized access - check if your token in the .env file is correct.';
28+
break;
29+
case 404:
30+
apiError.value = `404 Not Found - is the organization '${process.env.VUE_APP_GITHUB_ORG}' correct?`;
31+
break;
32+
default:
33+
apiError.value = error.message;
34+
break;
35+
}
36+
} else {
37+
// Update apiError with the error message
38+
apiError.value = error.message;
39+
}
40+
// Add a new line to the apiError message
41+
apiError.value += ' <br> If .env file is modified, restart the changes to take effect.';
42+
43+
});
44+
45+
return { apiError, metrics };
46+
}
47+
});
48+
</script>
49+
50+
<style scoped>
51+
52+
</style>

src/components/LanguagesBreakdown.vue

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
<template>
2+
<div>
3+
<!-- API Error Message -->
4+
<div v-if="apiError" class="error-message" v-html="apiError"></div>
5+
<div v-if="!apiError">
6+
<div class="tiles-container">
7+
<!-- Acceptance Rate Tile -->
8+
<v-card elevation="4" color="white" variant="elevated" class="mx-auto my-3" style="width: 300px; height: 175px;">
9+
<v-card-item>
10+
<div>
11+
<div class="text-overline mb-1" style="visibility: hidden;">filler</div>
12+
<div class="text-h6 mb-1">Number of languages</div>
13+
<div class="text-caption">
14+
Over the last 28 days
15+
</div>
16+
<p>{{ numberOfLanguages }}</p>
17+
</div>
18+
</v-card-item>
19+
</v-card>
20+
21+
</div>
22+
23+
<v-main class="p-1" style="min-height: 300px;">
24+
25+
<v-container style="min-height: 300px;" class="px-4 elevation-2">
26+
<v-card>
27+
<v-card-item class="d-flex justify-center align-center">
28+
<div class="text-overline mb-1" style="visibility: hidden;">filler</div>
29+
<div class="text-h6 mb-1">Top 5 languages by accepted prompts</div>
30+
<div style="width: 300px; height: 300px;" >
31+
<Pie :data="languagesChartDataTop5" :options="chartOptions" />
32+
</div>
33+
</v-card-item>
34+
</v-card>
35+
36+
<br>
37+
<h2>Languages Breakdown | Sorted by Accepted Lines of Code</h2>
38+
<br>
39+
40+
<v-data-table :headers="headers" :items="Array.from(languages)" class="elevation-2">
41+
<template v-slot:item="{item}">
42+
<tr>
43+
<td>{{ item[0] }}</td>
44+
<td>{{ item[1].acceptedPrompts }}</td>
45+
<td>{{ item[1].acceptedLinesOfCode }}</td>
46+
<td v-if="item[1].acceptanceRate !== undefined">{{ item[1].acceptanceRate.toFixed(2) }}%</td>
47+
</tr>
48+
</template>
49+
</v-data-table>
50+
</v-container>
51+
</v-main>
52+
</div>
53+
</div>
54+
</template>
55+
56+
<script lang="ts">
57+
import { defineComponent, ref } from 'vue';
58+
import { getGitHubCopilotMetricsApi } from '../api/GitHubApi';
59+
import { Metrics } from '../model/MetricsData';
60+
import { Language } from '../model/Language';
61+
import {
62+
Chart as ChartJS,
63+
ArcElement,
64+
CategoryScale,
65+
LinearScale,
66+
PointElement,
67+
LineElement,
68+
BarElement,
69+
Title,
70+
Tooltip,
71+
Legend
72+
} from 'chart.js'
73+
74+
import { Pie } from 'vue-chartjs'
75+
76+
ChartJS.register(
77+
ArcElement,
78+
CategoryScale,
79+
LinearScale,
80+
BarElement,
81+
PointElement,
82+
LineElement,
83+
Title,
84+
Tooltip,
85+
Legend
86+
)
87+
88+
89+
export default defineComponent({
90+
name: 'LanguagesBreakdown',
91+
components: {
92+
Pie
93+
},
94+
data() {
95+
return {
96+
headers: [
97+
{ title: 'Language Name', key: 'languageName' },
98+
{ title: 'Accepted Prompts', key: 'acceptedPrompts' },
99+
{ title: 'Accepted Lines of Code', key: 'acceptedLinesOfCode' },
100+
{ title: 'Acceptance Rate (%)', key: 'acceptanceRate' },
101+
],
102+
};
103+
},
104+
setup() {
105+
console.log('LanguagesBreakdown setup');
106+
107+
const metrics = ref<Metrics[]>([]);
108+
109+
// API Error Message
110+
const apiError = ref<string | null>(null);
111+
112+
// Create an empty map to store the languages.
113+
const languages = ref(new Map<string, Language>());
114+
115+
// Number of languages
116+
const numberOfLanguages = ref(0);
117+
118+
// Languages Chart Data for languages breakdown Pie Chart
119+
let languagesChartData = ref<{ labels: string[]; datasets: any[] }>({ labels: [], datasets: [] });
120+
121+
let languagesChartDataTop5 = ref<{ labels: string[]; datasets: any[] }>({ labels: [], datasets: [] });
122+
123+
const chartOptions = {
124+
responsive: true,
125+
maintainAspectRatio: true,
126+
};
127+
128+
getGitHubCopilotMetricsApi().then(data => {
129+
metrics.value = data;
130+
131+
// Process the language breakdown separately
132+
data.forEach(m => m.breakdown.forEach(breakdown =>
133+
{
134+
const languageName = breakdown.language;
135+
let language = languages.value.get(languageName);
136+
137+
if (!language) {
138+
// Create a new Language object if it does not exist
139+
language = new Language({
140+
name: languageName,
141+
acceptedPrompts: breakdown.acceptances_count,
142+
suggestedLinesOfCode: breakdown.lines_suggested,
143+
acceptedLinesOfCode: breakdown.lines_accepted,
144+
});
145+
languages.value.set(languageName, language);
146+
} else {
147+
// Update the existing Language object
148+
language.acceptedPrompts += breakdown.acceptances_count;
149+
language.suggestedLinesOfCode += breakdown.lines_suggested;
150+
language.acceptedLinesOfCode += breakdown.lines_accepted;
151+
}
152+
// Recalculate the acceptance rate
153+
language.acceptanceRate = language.suggestedLinesOfCode !== 0 ? (language.acceptedLinesOfCode / language.suggestedLinesOfCode) * 100 : 0;
154+
}));
155+
156+
//Sort languages map by accepted lines of code
157+
languages.value[Symbol.iterator] = function* () {
158+
yield* [...this.entries()].sort((a, b) => b[1].acceptedLinesOfCode - a[1].acceptedLinesOfCode);
159+
}
160+
161+
// Convert the Map to an array
162+
let languagesArray = Array.from(languages.value.entries());
163+
164+
// Sort the array
165+
languagesArray.sort((a, b) => b[1].acceptedLinesOfCode - a[1].acceptedLinesOfCode);
166+
167+
// Convert the array back to a Map
168+
//languages.value = new Map(languagesArray);
169+
170+
console.log("Languages before: " + Array.from(languages.value.values()).map(language => language.languageName));
171+
console.log("languages.value[0].languageName: " + Array.from(languages.value.values())[0].languageName);
172+
console.log("languages.value[1].languageName: " + Array.from(languages.value.values())[1].languageName);
173+
console.log("languages.value[2].languageName: " + Array.from(languages.value.values())[2].languageName);
174+
console.log("languages.value[3].languageName: " + Array.from(languages.value.values())[3].languageName);
175+
console.log("languages.value[4].languageName: " + Array.from(languages.value.values())[4].languageName);
176+
177+
languagesChartData.value = {
178+
labels: Array.from(languages.value.values()).map(language => language.languageName),
179+
datasets: [
180+
{
181+
data: Array.from(languages.value.values()).map(language => language.acceptedPrompts),
182+
backgroundColor: ['#41B883', '#E46651', '#00D8FF', '#DD1B16'],
183+
},
184+
],
185+
};
186+
187+
let top5Languages = Array.from(languages.value.values()).slice(110, 300);
188+
189+
languagesChartDataTop5.value = {
190+
labels: Array.from(top5Languages.values()).map(language => language.languageName),
191+
datasets: [
192+
{
193+
data: Array.from(top5Languages.values()).map(language => language.acceptedPrompts),
194+
backgroundColor: ['#41B883', '#E46651', '#00D8FF', '#DD1B16'],
195+
},
196+
],
197+
};
198+
199+
numberOfLanguages.value = languages.value.size;
200+
201+
console.log("Number of languages: " + numberOfLanguages.value);
202+
203+
console.log("LanguagesChartData: " + JSON.stringify(languagesChartData));
204+
205+
206+
}).catch(error => {
207+
console.log(error);
208+
// Check the status code of the error response
209+
if (error.response && error.response.status) {
210+
switch (error.response.status) {
211+
case 401:
212+
apiError.value = '401 Unauthorized access - check if your token in the .env file is correct.';
213+
break;
214+
case 404:
215+
apiError.value = `404 Not Found - is the organization '${process.env.VUE_APP_GITHUB_ORG}' correct?`;
216+
break;
217+
default:
218+
apiError.value = error.message;
219+
break;
220+
}
221+
} else {
222+
// Update apiError with the error message
223+
apiError.value = error.message;
224+
}
225+
// Add a new line to the apiError message
226+
apiError.value += ' <br> If .env file is modified, restart the changes to take effect.';
227+
228+
});
229+
230+
return { apiError, chartOptions, languages, numberOfLanguages, languagesChartData, languagesChartDataTop5 };
231+
},
232+
233+
234+
});
235+
</script>
236+
237+
<style scoped>
238+
.error-message {
239+
color: red;
240+
}
241+
242+
.center-table {
243+
margin-left: auto;
244+
margin-right: auto;
245+
}
246+
247+
.tiles-container {
248+
display: flex;
249+
justify-content: flex-start;
250+
flex-wrap: wrap;
251+
}
252+
253+
.tile {
254+
border: 1px solid #ccc;
255+
box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.1),
256+
-3px -3px 5px rgba(255, 255, 255, 0.7);
257+
padding: 20px;
258+
border-radius: 10px;
259+
width: 20%;
260+
margin: 1%;
261+
}
262+
</style>

src/components/MainComponent.vue

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<template>
22
<v-card>
3-
<v-toolbar color="black" elevation="4">
3+
<v-toolbar color="indigo" elevation="4">
44
<v-btn icon>
55
<v-icon>mdi-github</v-icon>
66
</v-btn>
77

8-
<v-toolbar-title>Copilot Metrics Viewer</v-toolbar-title>
9-
8+
<v-toolbar-title>Copilot Metrics Viewer | {{ GitHubOrgName }}</v-toolbar-title>
9+
<h2> </h2>
1010
<v-spacer></v-spacer>
1111

1212
<template v-slot:extension>
@@ -17,32 +17,42 @@
1717
</v-tabs>
1818
</template>
1919
</v-toolbar>
20-
21-
<v-window v-model="tab">
22-
<v-window-item v-for="item in items" :key="item" :value="item">
23-
<v-card flat>
24-
<component :is="item === 'organization' ? 'MetricsViewer' : null"></component>
25-
</v-card>
26-
</v-window-item>
27-
</v-window>
20+
<v-window v-model="tab">
21+
<v-window-item v-for="item in items" :key="item" :value="item">
22+
<v-card flat>
23+
<MetricsViewer v-if="item === 'organization'" />
24+
<LanguagesBreakdown v-if="item === 'languages'" />
25+
<CopilotChatViewer v-if="item === 'Copilot chat'" />
26+
</v-card>
27+
</v-window-item>
28+
</v-window>
2829
</v-card>
2930
</template>
3031

3132
<script lang='ts'>
3233
import { defineComponent } from 'vue'
3334
import MetricsViewer from './MetricsViewer.vue' // adjust the path as needed
35+
import LanguagesBreakdown from './LanguagesBreakdown.vue' // adjust the path as needed
36+
import CopilotChatViewer from './CopilotChatViewer.vue' // adjust the path as needed
3437
3538
3639
export default defineComponent({
3740
name: 'MainComponent',
3841
components: {
3942
MetricsViewer,
43+
LanguagesBreakdown,
44+
CopilotChatViewer
45+
},
46+
computed: {
47+
GitHubOrgName() {
48+
return process.env.VUE_APP_GITHUB_ORG;
49+
}
4050
},
41-
4251
data () {
4352
return {
44-
items: ['organization', 'enterprise', 'languages', 'Copilot chat'],
53+
items: ['organization', 'languages', 'Copilot chat'],
4554
tab: null,
55+
4656
}
4757
},
4858
})

0 commit comments

Comments
 (0)