Skip to content

Commit 6a56e0e

Browse files
authored
Merge pull request #11 from github-copilot-community/feature/toolbar-tabs
Feature/toolbar tabs
2 parents 61a27bb + 73aea1d commit 6a56e0e

File tree

4 files changed

+345
-35
lines changed

4 files changed

+345
-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: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
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+
languagesChartData.value = {
162+
labels: Array.from(languages.value.values()).map(language => language.languageName),
163+
datasets: [
164+
{
165+
data: Array.from(languages.value.values()).map(language => language.acceptedPrompts),
166+
backgroundColor: ['#41B883', '#E46651', '#00D8FF', '#DD1B16'],
167+
},
168+
],
169+
};
170+
171+
// Get the top 5 languages by accepted prompts
172+
const top5Languages = new Map([...languages.value].slice(0, 5));
173+
174+
languagesChartDataTop5.value = {
175+
labels: Array.from(top5Languages.values()).map(language => language.languageName),
176+
datasets: [
177+
{
178+
data: Array.from(top5Languages.values()).map(language => language.acceptedPrompts),
179+
backgroundColor: ['#41B883', '#E46651', '#00D8FF', '#DD1B16'],
180+
},
181+
],
182+
};
183+
184+
numberOfLanguages.value = languages.value.size;
185+
186+
console.log("Number of languages: " + numberOfLanguages.value);
187+
188+
console.log("LanguagesChartData: " + JSON.stringify(languagesChartData));
189+
190+
191+
}).catch(error => {
192+
console.log(error);
193+
// Check the status code of the error response
194+
if (error.response && error.response.status) {
195+
switch (error.response.status) {
196+
case 401:
197+
apiError.value = '401 Unauthorized access - check if your token in the .env file is correct.';
198+
break;
199+
case 404:
200+
apiError.value = `404 Not Found - is the organization '${process.env.VUE_APP_GITHUB_ORG}' correct?`;
201+
break;
202+
default:
203+
apiError.value = error.message;
204+
break;
205+
}
206+
} else {
207+
// Update apiError with the error message
208+
apiError.value = error.message;
209+
}
210+
// Add a new line to the apiError message
211+
apiError.value += ' <br> If .env file is modified, restart the changes to take effect.';
212+
213+
});
214+
215+
return { apiError, chartOptions, languages, numberOfLanguages, languagesChartData, languagesChartDataTop5 };
216+
},
217+
218+
219+
});
220+
</script>
221+
222+
<style scoped>
223+
.error-message {
224+
color: red;
225+
}
226+
227+
.center-table {
228+
margin-left: auto;
229+
margin-right: auto;
230+
}
231+
232+
.tiles-container {
233+
display: flex;
234+
justify-content: flex-start;
235+
flex-wrap: wrap;
236+
}
237+
238+
.tile {
239+
border: 1px solid #ccc;
240+
box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.1),
241+
-3px -3px 5px rgba(255, 255, 255, 0.7);
242+
padding: 20px;
243+
border-radius: 10px;
244+
width: 20%;
245+
margin: 1%;
246+
}
247+
</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)