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
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,18 @@ The language breakdown analysis tab also displays a table showing the Accepted P
4. **Total Active Copilot Chat Users:** a bar chart that illustrates the total number of users who have actively interacted with Copilot over the past 28 days.

## Seat Analysis
<p align="center">
<img width="800" alt="image" src="https://github.com/github-copilot-resources/copilot-metrics-viewer/assets/54096296/51747194-df30-4bfb-8849-54a0510fffcb">
</p>
1. **Total Assigned:** This metric represents the total number of Copilot seats assigned within current organization/enterprise.

![image](https://github.com/DevOps-zhuang/copilot-metrics-viewer/assets/54096296/d1fa9d1d-4fab-4e87-84ba-7be189dd4dd0)

1. **Total Assigned:** This metric represents the total number of Copilot seats assigned within current organization.

2. **Assigned But Never Used:** This metric shows seats that were assigned but never within the current organization. The assigned timestamp is also displayed in the below chart.
2. **Assigned But Not Used (in the last 28 days):** This metric shows seats that were assigned but not used during the last 28 days within the current organization/enterprise. The assigned timestamp is also displayed in the chart.”

3. **No Activity in the Last 7 days:** never used seats or seats used, but with no activity in the past 7 days.

4. **No Activity in the last 7 days (including never used seats):** a table to display seats that have had no activity in the past 7 days, ordered by the date of last activity. Seats that were used earlier are displayed at the top.



## Setup instructions

In the `.env` file, you can configure several environment variables that control the behavior of the application.
Expand Down
92 changes: 57 additions & 35 deletions src/api/ExtractSeats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,64 @@ import enterpriseMockedResponse_seats from '../assets/enterprise_response_sample
export const getSeatsApi = async (): Promise<Seat[]> => {
const perPage = 50;
let page = 1;
let seatUrl = `https://api.github.com/`;
let seatsData: Seat[] = [];

let response;
if (process.env.VUE_APP_SCOPE !== "organization") {
// when the scope is not organization, return seatsData,by default it will return empty array
return seatsData;
}
else{
if (process.env.VUE_APP_MOCKED_DATA === "true") {
response = organizationMockedResponse_seats;


if (process.env.VUE_APP_MOCKED_DATA === "true") {
console.log("Using mock data. Check VUE_APP_MOCKED_DATA variable.");
if (process.env.VUE_APP_SCOPE === "organization") {
response = organizationMockedResponse_seats;
}
else if (process.env.VUE_APP_SCOPE === "enterprise") {
response = enterpriseMockedResponse_seats;
}
else {
throw new Error(`Invalid VUE_APP_SCOPE value: ${process.env.VUE_APP_SCOPE}. Expected "organization" or "enterprise".`);
}
seatsData = seatsData.concat(response.seats.map((item: any) => new Seat(item)));
}
else if (process.env.VUE_APP_MOCKED_DATA === "false") {
return seatsData;
}
else {
// if VUE_APP_GITHUB_TOKEN is not set, throw an error
if (!process.env.VUE_APP_GITHUB_TOKEN) {
throw new Error("VUE_APP_GITHUB_TOKEN environment variable is not set.");
return seatsData;
}
else if (process.env.VUE_APP_SCOPE === "organization") {
seatUrl=seatUrl+`orgs/${process.env.VUE_APP_GITHUB_ORG}/copilot/billing/seats`;
}
else if (process.env.VUE_APP_SCOPE === "enterprise") {
seatUrl=seatUrl+`enterprises/${process.env.VUE_APP_GITHUB_ENT}/copilot/billing/seats`;
}
else {
throw new Error(`Invalid VUE_APP_SCOPE value: ${process.env.VUE_APP_SCOPE}. Expected "organization" or "enterprise".`);
return seatsData;
}

// Fetch the first page to get the total number of seats
response = await axios.get(`https://api.github.com/orgs/${process.env.VUE_APP_GITHUB_ORG}/copilot/billing/seats`, {
response = await axios.get(seatUrl, {
headers: {
Accept: "application/vnd.github+json",
Authorization: `Bearer ${process.env.VUE_APP_GITHUB_TOKEN}`,
"X-GitHub-Api-Version": "2022-11-28",
},
params: {
per_page: perPage,
page: page
}
});

seatsData = seatsData.concat(response.data.seats.map((item: any) => new Seat(item)));
// Calculate the total pages
const totalSeats = response.data.total_seats;
const totalPages = Math.ceil(totalSeats / perPage);

// Fetch the remaining pages
for (page = 2; page <= totalPages; page++) {
response = await axios.get(seatUrl, {
headers: {
Accept: "application/vnd.github+json",
Authorization: `Bearer ${process.env.VUE_APP_GITHUB_TOKEN}`,
Expand All @@ -33,29 +76,8 @@ export const getSeatsApi = async (): Promise<Seat[]> => {
page: page
}
});

seatsData = seatsData.concat(response.data.seats.map((item: any) => new Seat(item)));
// Calculate the total pages
const totalSeats = response.data.total_seats;
const totalPages = Math.ceil(totalSeats / perPage);

// Fetch the remaining pages
for (page = 2; page <= totalPages; page++) {
response = await axios.get(`https://api.github.com/orgs/${process.env.VUE_APP_GITHUB_ORG}/copilot/billing/seats`, {
headers: {
Accept: "application/vnd.github+json",
Authorization: `Bearer ${process.env.VUE_APP_GITHUB_TOKEN}`,
"X-GitHub-Api-Version": "2022-11-28",
},
params: {
per_page: perPage,
page: page
}
});

seatsData = seatsData.concat(response.data.seats.map((item: any) => new Seat(item)));
} //end of else if (process.env.VUE_APP_MOCKED_DATA === "false")
} //end of else if (process.env.VUE_APP_SCOPE !== "organization")
return seatsData;
}
}
}
return seatsData;
}
}
2 changes: 1 addition & 1 deletion src/assets/enterprise_response_sample_seats.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"last_activity_at": "2021-10-14T00:53:32-06:00",
"last_activity_editor": "vscode/1.77.3/copilot/1.86.82",
"assignee": {
"login": "octocat",
"login": "octocat_byEnterprise",
"id": 1,
"node_id": "MDQ6VXNlcjE=",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
Expand Down
4 changes: 2 additions & 2 deletions src/assets/organization_response_sample_seats.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"last_activity_at": "2021-10-14T00:53:32-06:00",
"last_activity_editor": "vscode/1.77.3/copilot/1.86.82",
"assignee": {
"login": "octocat",
"login": "octocat_org",
"id": 1,
"node_id": "MDQ6VXNlcjE=",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
Expand Down Expand Up @@ -50,7 +50,7 @@
"last_activity_at": "2021-10-13T00:53:32-06:00",
"last_activity_editor": "vscode/1.77.3/copilot/1.86.82",
"assignee": {
"login": "octokitten",
"login": "octokitten_org",
"id": 1,
"node_id": "MDQ76VNlcjE=",
"avatar_url": "https://github.com/images/error/octokitten_happy.gif",
Expand Down
3 changes: 1 addition & 2 deletions src/components/ApiResponse.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</div>

<br><br>
<div v-if="vueAppScope === 'organization'">

<v-card max-height="575px" class="overflow-y-auto">
<pre ref="jsonText">{{ JSON.stringify(seats, null, 2) }}</pre>
</v-card>
Expand All @@ -24,7 +24,6 @@
<div v-if="showSeatMessage" :class="{'copy-message': true, 'error': isError}">{{ message }}</div>
</transition>
</div>
</div>
</v-container>
</template>

Expand Down
13 changes: 1 addition & 12 deletions src/components/MainComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@
<BreakdownComponent v-if="item === 'languages'" :metrics="metrics" :breakdownKey="'language'"/>
<BreakdownComponent v-if="item === 'editors'" :metrics="metrics" :breakdownKey="'editor'"/>
<CopilotChatViewer v-if="item === 'copilot chat'" :metrics="metrics" />
<div v-if="isScopeOrganization">
<SeatsAnalysisViewer v-if="item === 'seat analysis'" :seats="seats" />
</div>
<ApiResponse v-if="item === 'api response'" :metrics="metrics" :seats="seats" />
</v-card>
</v-window-item>
Expand Down Expand Up @@ -94,23 +92,14 @@ export default defineComponent({
},
data () {
return {
tabItems: ['languages', 'editors', 'copilot chat', 'api response'],
tabItems: ['languages', 'editors', 'copilot chat','seat analysis' , 'api response'],
tab: null
}
},
created() {
if(this.itemName !== 'invalid'){
this.tabItems.unshift(this.itemName);
}
if (process.env.VUE_APP_SCOPE === 'organization') {
// get the last item in the array,which is 'api response'
//and add 'seat analysis' before it
let lastItem = this.tabItems.pop();
this.tabItems.push('seat analysis');
if (lastItem) {
this.tabItems.push(lastItem);
}
}
},
setup() {
const metricsReady = ref(false);
Expand Down
8 changes: 5 additions & 3 deletions src/components/SeatsAnalysisViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
<v-card-item class="d-flex justify-center align-center">
<div class="tiles-text">
<div class="text-overline mb-1" style="visibility: hidden;">filler</div>
<div class="text-h6 mb-1">Assigned But Never Used</div>
<div class="text-h6 mb-1">Assigned But Not Used</div>
<div class="text-caption">
No show seats
No show seats in the past 28 days
</div>
<p class="text-h4">{{ NoshowSeats.length }}</p>
</div>
Expand Down Expand Up @@ -50,6 +50,7 @@
<tr>
<td>{{ item.login }}</td>
<td>{{ item.id }}</td>
<td>{{ item.team }}</td>
<td>{{ item.created_at }}</td>
<td>{{ item.last_activity_at }}</td>
<td>{{ item.last_activity_editor }}</td>
Expand Down Expand Up @@ -103,7 +104,8 @@ data() {
headers: [
{ title: 'Login', key: 'login' },
{ title: 'GitHub ID', key: 'id' },
{ title: 'Assigned to the Organization At', key: 'created_at' },
{ title: 'Assigning team', key: 'team' },
{ title: 'Assigned time', key: 'created_at' },
{ title: 'Last Activity At', key: 'last_activity_at' },
{ title: 'Last Activity Editor', key: 'last_activity_editor' },
],
Expand Down
4 changes: 3 additions & 1 deletion src/model/Seat.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
export class Seat {
login: string;
id: number;
team: string;
created_at: string;
last_activity_at: string;
last_activity_editor: string;

constructor(data: any) {
this.login = data.assignee.login;
this.id = data.assignee.id;
this.created_at = data.created_at;
this.team = data.assigning_team ? data.assigning_team.name : '';
this.created_at = data.created_at;
this.last_activity_at = data.last_activity_at;
this.last_activity_editor = data.last_activity_editor;
}
Expand Down