@backstage-community/plugin-copilot-backend 0.15.3 → 0.15.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @backstage-community/plugin-copilot-backend
2
2
 
3
+ ## 0.15.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 0a904fd: Normalized date format in task logs. Dates now display in ISO 8601 UTC format (`2025-11-03T00:00:00.000Z`) instead of locale-specific format (`Mon Nov 03 2025 01:00:00 GMT+0100 (Central European Standard Time)`).
8
+
9
+ ## 0.15.4
10
+
11
+ ### Patch Changes
12
+
13
+ - 14bf95c: Improves error logging. Changes Octokit to use the built in CreateAppAuth in order to automatically refresh expired tokens.
14
+
3
15
  ## 0.15.3
4
16
 
5
17
  ### Patch Changes
@@ -1,34 +1,78 @@
1
1
  'use strict';
2
2
 
3
- var errors = require('@backstage/errors');
4
3
  var rest = require('@octokit/rest');
4
+ var authApp = require('@octokit/auth-app');
5
5
  var GithubUtils = require('../utils/GithubUtils.cjs.js');
6
6
 
7
7
  class GithubClient {
8
- constructor(copilotConfig, config) {
8
+ constructor(copilotConfig, config, logger) {
9
9
  this.copilotConfig = copilotConfig;
10
10
  this.config = config;
11
+ this.logger = logger;
11
12
  }
12
13
  enterpriseOctokit;
13
14
  organizationOctokit;
14
- static async fromConfig(config) {
15
+ static async fromConfig(config, logger) {
15
16
  const info = GithubUtils.getCopilotConfig(config);
16
- return new GithubClient(info, config);
17
+ return new GithubClient(info, config, logger);
17
18
  }
18
19
  async getCredentials() {
19
20
  return await GithubUtils.getGithubCredentials(this.config, this.copilotConfig);
20
21
  }
21
22
  async getOctokit(type) {
22
23
  const credentials = await this.getCredentials();
23
- const headers = credentials[type]?.headers || {};
24
- return new rest.Octokit({
24
+ const authStrategy = credentials[type];
25
+ if (!authStrategy) {
26
+ throw new Error(
27
+ `No credentials configured for ${type}. Please configure GitHub integration for ${type === "organization" ? this.copilotConfig.organization : this.copilotConfig.enterprise} in your app-config.`
28
+ );
29
+ }
30
+ const octokitConfig = {
25
31
  baseUrl: this.copilotConfig.apiBaseUrl,
26
- auth: headers.Authorization?.replace("Bearer ", "") || "",
27
32
  headers: {
28
33
  Accept: "application/vnd.github+json",
29
34
  "X-GitHub-Api-Version": "2022-11-28"
30
35
  }
31
- });
36
+ };
37
+ if (typeof authStrategy === "string") {
38
+ this.logger.debug(
39
+ `[GithubClient] Using token authentication for ${type}`
40
+ );
41
+ octokitConfig.auth = authStrategy;
42
+ } else {
43
+ this.logger.debug(
44
+ `[GithubClient] Using GitHub App authentication for ${type}`,
45
+ {
46
+ appId: authStrategy.appId
47
+ }
48
+ );
49
+ const appOctokit = new rest.Octokit({
50
+ authStrategy: authApp.createAppAuth,
51
+ auth: {
52
+ appId: authStrategy.appId,
53
+ privateKey: authStrategy.privateKey
54
+ }
55
+ });
56
+ const orgName = type === "organization" ? this.copilotConfig.organization : this.copilotConfig.enterprise;
57
+ if (!orgName) {
58
+ throw new Error(
59
+ `No ${type} name configured in your app-config. Please see the documentation for setup instructions.`
60
+ );
61
+ }
62
+ const { data: installation } = await appOctokit.rest.apps.getOrgInstallation({
63
+ org: orgName
64
+ });
65
+ this.logger.debug(
66
+ `[GithubClient] Got installation ID ${installation.id} for ${orgName}`
67
+ );
68
+ octokitConfig.authStrategy = authApp.createAppAuth;
69
+ octokitConfig.auth = {
70
+ appId: authStrategy.appId,
71
+ privateKey: authStrategy.privateKey,
72
+ installationId: installation.id
73
+ };
74
+ }
75
+ return new rest.Octokit(octokitConfig);
32
76
  }
33
77
  async getEnterpriseOctokit() {
34
78
  if (!this.enterpriseOctokit) {
@@ -45,176 +89,144 @@ class GithubClient {
45
89
  async fetchEnterpriseCopilotMetrics() {
46
90
  const octokit = await this.getEnterpriseOctokit();
47
91
  const path = `/enterprises/${this.copilotConfig.enterprise}/copilot/metrics`;
48
- try {
49
- const response = await octokit.request(`GET ${path}`);
50
- return response.data;
51
- } catch (error) {
52
- throw errors.ResponseError.fromResponse(error.response || error);
53
- }
92
+ const response = await octokit.request(`GET ${path}`);
93
+ return response.data;
54
94
  }
55
95
  async fetchEnterpriseTeamCopilotMetrics(teamId) {
56
96
  const octokit = await this.getEnterpriseOctokit();
57
97
  const path = `/enterprises/${this.copilotConfig.enterprise}/team/${teamId}/copilot/metrics`;
58
- try {
59
- const response = await octokit.request(`GET ${path}`);
60
- return response.data;
61
- } catch (error) {
62
- throw errors.ResponseError.fromResponse(error.response || error);
63
- }
98
+ const response = await octokit.request(`GET ${path}`);
99
+ return response.data;
64
100
  }
65
101
  async fetchEnterpriseTeams() {
66
102
  const octokit = await this.getEnterpriseOctokit();
67
- try {
68
- const teams = [];
69
- let cursor = null;
70
- let hasNextPage = true;
71
- while (hasNextPage) {
72
- const query = `
73
- query($enterprise: String!, $cursor: String) {
74
- enterprise(slug: $enterprise) {
75
- organizations(first: 100) {
76
- nodes {
77
- teams(first: 100, after: $cursor) {
78
- pageInfo {
79
- hasNextPage
80
- endCursor
81
- }
82
- nodes {
83
- id
84
- databaseId
85
- slug
86
- name
87
- members {
88
- totalCount
89
- }
103
+ const teams = [];
104
+ let cursor = null;
105
+ let hasNextPage = true;
106
+ while (hasNextPage) {
107
+ const query = `
108
+ query($enterprise: String!, $cursor: String) {
109
+ enterprise(slug: $enterprise) {
110
+ organizations(first: 100) {
111
+ nodes {
112
+ teams(first: 100, after: $cursor) {
113
+ pageInfo {
114
+ hasNextPage
115
+ endCursor
116
+ }
117
+ nodes {
118
+ id
119
+ databaseId
120
+ slug
121
+ name
122
+ members {
123
+ totalCount
90
124
  }
91
125
  }
92
126
  }
93
127
  }
94
128
  }
95
129
  }
96
- `;
97
- const variables = {
98
- enterprise: this.copilotConfig.enterprise,
99
- cursor
100
- };
101
- const response = await octokit.graphql(query, variables);
102
- const allTeams = response.enterprise.organizations.nodes.flatMap(
103
- (org) => org.teams.nodes
104
- );
105
- const filteredTeams = allTeams.filter((team) => team.members.totalCount >= 5).map((team) => ({
106
- id: team.databaseId,
107
- slug: team.slug,
108
- name: team.name
109
- }));
110
- teams.push(...filteredTeams);
111
- hasNextPage = response.enterprise.organizations.nodes.some(
112
- (org) => org.teams.pageInfo.hasNextPage
113
- );
114
- if (hasNextPage) {
115
- cursor = response.enterprise.organizations.nodes.find(
116
- (org) => org.teams.pageInfo.hasNextPage
117
- )?.teams.pageInfo.endCursor;
118
130
  }
131
+ `;
132
+ const variables = {
133
+ enterprise: this.copilotConfig.enterprise,
134
+ cursor
135
+ };
136
+ const response = await octokit.graphql(query, variables);
137
+ const allTeams = response.enterprise.organizations.nodes.flatMap(
138
+ (org) => org.teams.nodes
139
+ );
140
+ const filteredTeams = allTeams.filter((team) => team.members.totalCount >= 5).map((team) => ({
141
+ id: team.databaseId,
142
+ slug: team.slug,
143
+ name: team.name
144
+ }));
145
+ teams.push(...filteredTeams);
146
+ hasNextPage = response.enterprise.organizations.nodes.some(
147
+ (org) => org.teams.pageInfo.hasNextPage
148
+ );
149
+ if (hasNextPage) {
150
+ cursor = response.enterprise.organizations.nodes.find(
151
+ (org) => org.teams.pageInfo.hasNextPage
152
+ )?.teams.pageInfo.endCursor;
119
153
  }
120
- return teams;
121
- } catch (error) {
122
- throw errors.ResponseError.fromResponse(error.response || error);
123
154
  }
155
+ return teams;
124
156
  }
125
157
  async fetchEnterpriseSeats() {
126
158
  const octokit = await this.getEnterpriseOctokit();
127
159
  const path = `/enterprises/${this.copilotConfig.enterprise}/copilot/billing/seats`;
128
- try {
129
- const seats = await octokit.paginate(`GET ${path}`, {
130
- per_page: 100
131
- // Maximum allowed per page
132
- });
133
- return this.mergePaginationResult(seats);
134
- } catch (error) {
135
- throw errors.ResponseError.fromResponse(error.response || error);
136
- }
160
+ const seats = await octokit.paginate(`GET ${path}`, {
161
+ per_page: 100
162
+ // Maximum allowed per page
163
+ });
164
+ return this.mergePaginationResult(seats);
137
165
  }
138
166
  async fetchOrganizationCopilotMetrics() {
139
167
  const octokit = await this.getOrganizationOctokit();
140
168
  const path = `/orgs/${this.copilotConfig.organization}/copilot/metrics`;
141
- try {
142
- const response = await octokit.request(`GET ${path}`);
143
- return response.data;
144
- } catch (error) {
145
- throw errors.ResponseError.fromResponse(error.response || error);
146
- }
169
+ const response = await octokit.request(`GET ${path}`);
170
+ return response.data;
147
171
  }
148
172
  async fetchOrganizationTeamCopilotMetrics(teamId) {
149
173
  const octokit = await this.getOrganizationOctokit();
150
174
  const path = `/orgs/${this.copilotConfig.organization}/team/${teamId}/copilot/metrics`;
151
- try {
152
- const response = await octokit.request(`GET ${path}`);
153
- return response.data;
154
- } catch (error) {
155
- throw errors.ResponseError.fromResponse(error.response || error);
156
- }
175
+ const response = await octokit.request(`GET ${path}`);
176
+ return response.data;
157
177
  }
158
178
  async fetchOrganizationTeams() {
159
179
  const octokit = await this.getOrganizationOctokit();
160
- try {
161
- const teams = [];
162
- let cursor = null;
163
- let hasNextPage = true;
164
- while (hasNextPage) {
165
- const query = `
166
- query($org: String!, $cursor: String) {
167
- organization(login: $org) {
168
- teams(first: 100, after: $cursor) {
169
- pageInfo {
170
- hasNextPage
171
- endCursor
172
- }
173
- nodes {
174
- id
175
- databaseId
176
- slug
177
- name
178
- members {
179
- totalCount
180
- }
180
+ const teams = [];
181
+ let cursor = null;
182
+ let hasNextPage = true;
183
+ while (hasNextPage) {
184
+ const query = `
185
+ query($org: String!, $cursor: String) {
186
+ organization(login: $org) {
187
+ teams(first: 100, after: $cursor) {
188
+ pageInfo {
189
+ hasNextPage
190
+ endCursor
191
+ }
192
+ nodes {
193
+ id
194
+ databaseId
195
+ slug
196
+ name
197
+ members {
198
+ totalCount
181
199
  }
182
200
  }
183
201
  }
184
202
  }
185
- `;
186
- const variables = {
187
- org: this.copilotConfig.organization,
188
- cursor
189
- };
190
- const response = await octokit.graphql(query, variables);
191
- const teamsData = response.organization.teams;
192
- const filteredTeams = teamsData.nodes.filter((team) => team.members.totalCount >= 5).map((team) => ({
193
- id: team.databaseId,
194
- slug: team.slug,
195
- name: team.name
196
- }));
197
- teams.push(...filteredTeams);
198
- hasNextPage = teamsData.pageInfo.hasNextPage;
199
- cursor = teamsData.pageInfo.endCursor;
200
- }
201
- return teams;
202
- } catch (error) {
203
- throw errors.ResponseError.fromResponse(error.response || error);
203
+ }
204
+ `;
205
+ const variables = {
206
+ org: this.copilotConfig.organization,
207
+ cursor
208
+ };
209
+ const response = await octokit.graphql(query, variables);
210
+ const teamsData = response.organization.teams;
211
+ const filteredTeams = teamsData.nodes.filter((team) => team.members.totalCount >= 5).map((team) => ({
212
+ id: team.databaseId,
213
+ slug: team.slug,
214
+ name: team.name
215
+ }));
216
+ teams.push(...filteredTeams);
217
+ hasNextPage = teamsData.pageInfo.hasNextPage;
218
+ cursor = teamsData.pageInfo.endCursor;
204
219
  }
220
+ return teams;
205
221
  }
206
222
  async fetchOrganizationSeats() {
207
223
  const octokit = await this.getOrganizationOctokit();
208
- try {
209
- const seats = await octokit.paginate(octokit.copilot.listCopilotSeats, {
210
- org: this.copilotConfig.organization,
211
- per_page: 100
212
- // Maximum allowed per page
213
- });
214
- return this.mergePaginationResult(seats);
215
- } catch (error) {
216
- throw errors.ResponseError.fromResponse(error.response || error);
217
- }
224
+ const seats = await octokit.paginate(octokit.copilot.listCopilotSeats, {
225
+ org: this.copilotConfig.organization,
226
+ per_page: 100
227
+ // Maximum allowed per page
228
+ });
229
+ return this.mergePaginationResult(seats);
218
230
  }
219
231
  /**
220
232
  * This function is used to merge paginated results from the GitHub API
@@ -1 +1 @@
1
- {"version":3,"file":"GithubClient.cjs.js","sources":["../../src/client/GithubClient.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ResponseError } from '@backstage/errors';\nimport { Config } from '@backstage/config';\nimport {\n CopilotMetrics,\n CopilotSeats,\n TeamInfo,\n} from '@backstage-community/plugin-copilot-common';\nimport { Octokit } from '@octokit/rest';\nimport {\n CopilotConfig,\n CopilotCredentials,\n getCopilotConfig,\n getGithubCredentials,\n} from '../utils/GithubUtils';\n\ninterface GithubApi {\n fetchEnterpriseCopilotMetrics: () => Promise<CopilotMetrics[]>;\n fetchEnterpriseTeamCopilotMetrics: (\n teamId: string,\n ) => Promise<CopilotMetrics[]>;\n fetchOrganizationCopilotMetrics: () => Promise<CopilotMetrics[]>;\n fetchOrganizationTeamCopilotMetrics: (\n teamId: string,\n ) => Promise<CopilotMetrics[]>;\n\n fetchEnterpriseTeams: () => Promise<TeamInfo[]>;\n fetchOrganizationTeams: () => Promise<TeamInfo[]>;\n}\n\nexport class GithubClient implements GithubApi {\n private enterpriseOctokit?: Octokit;\n private organizationOctokit?: Octokit;\n\n constructor(\n private readonly copilotConfig: CopilotConfig,\n private readonly config: Config,\n ) {}\n\n static async fromConfig(config: Config) {\n const info = getCopilotConfig(config);\n return new GithubClient(info, config);\n }\n\n private async getCredentials(): Promise<CopilotCredentials> {\n return await getGithubCredentials(this.config, this.copilotConfig);\n }\n\n private async getOctokit(\n type: 'enterprise' | 'organization',\n ): Promise<Octokit> {\n const credentials = await this.getCredentials();\n const headers = credentials[type]?.headers || {};\n\n return new Octokit({\n baseUrl: this.copilotConfig.apiBaseUrl,\n auth: headers.Authorization?.replace('Bearer ', '') || '',\n headers: {\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n },\n });\n }\n\n private async getEnterpriseOctokit(): Promise<Octokit> {\n if (!this.enterpriseOctokit) {\n this.enterpriseOctokit = await this.getOctokit('enterprise');\n }\n return this.enterpriseOctokit;\n }\n\n private async getOrganizationOctokit(): Promise<Octokit> {\n if (!this.organizationOctokit) {\n this.organizationOctokit = await this.getOctokit('organization');\n }\n return this.organizationOctokit;\n }\n\n async fetchEnterpriseCopilotMetrics(): Promise<CopilotMetrics[]> {\n const octokit = await this.getEnterpriseOctokit();\n const path = `/enterprises/${this.copilotConfig.enterprise}/copilot/metrics`;\n\n try {\n const response = await octokit.request(`GET ${path}`);\n return response.data as CopilotMetrics[];\n } catch (error) {\n throw ResponseError.fromResponse(error.response || error);\n }\n }\n\n async fetchEnterpriseTeamCopilotMetrics(\n teamId: string,\n ): Promise<CopilotMetrics[]> {\n const octokit = await this.getEnterpriseOctokit();\n const path = `/enterprises/${this.copilotConfig.enterprise}/team/${teamId}/copilot/metrics`;\n\n try {\n const response = await octokit.request(`GET ${path}`);\n return response.data as CopilotMetrics[];\n } catch (error) {\n throw ResponseError.fromResponse(error.response || error);\n }\n }\n\n async fetchEnterpriseTeams(): Promise<TeamInfo[]> {\n const octokit = await this.getEnterpriseOctokit();\n\n try {\n const teams: TeamInfo[] = [];\n let cursor: string | null = null;\n let hasNextPage = true;\n\n while (hasNextPage) {\n const query = `\n query($enterprise: String!, $cursor: String) {\n enterprise(slug: $enterprise) {\n organizations(first: 100) {\n nodes {\n teams(first: 100, after: $cursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n databaseId\n slug\n name\n members {\n totalCount\n }\n }\n }\n }\n }\n }\n }\n `;\n\n const variables = {\n enterprise: this.copilotConfig.enterprise,\n cursor,\n };\n\n const response: any = await octokit.graphql(query, variables);\n\n // Flatten teams from all organizations in the enterprise\n const allTeams = response.enterprise.organizations.nodes.flatMap(\n (org: any) => org.teams.nodes,\n );\n\n // Filter teams with 5 or more members\n const filteredTeams = allTeams\n .filter((team: any) => team.members.totalCount >= 5)\n .map((team: any) => ({\n id: team.databaseId,\n slug: team.slug,\n name: team.name,\n }));\n\n teams.push(...filteredTeams);\n\n // Check if any organization has more teams to fetch\n hasNextPage = response.enterprise.organizations.nodes.some(\n (org: any) => org.teams.pageInfo.hasNextPage,\n );\n\n if (hasNextPage) {\n cursor = response.enterprise.organizations.nodes.find(\n (org: any) => org.teams.pageInfo.hasNextPage,\n )?.teams.pageInfo.endCursor;\n }\n }\n\n return teams;\n } catch (error) {\n throw ResponseError.fromResponse(error.response || error);\n }\n }\n\n async fetchEnterpriseSeats(): Promise<any> {\n const octokit = await this.getEnterpriseOctokit();\n const path = `/enterprises/${this.copilotConfig.enterprise}/copilot/billing/seats`;\n\n try {\n const seats = await octokit.paginate(`GET ${path}`, {\n per_page: 100, // Maximum allowed per page\n });\n\n return this.mergePaginationResult(seats as CopilotSeats[]);\n } catch (error) {\n throw ResponseError.fromResponse(error.response || error);\n }\n }\n\n async fetchOrganizationCopilotMetrics(): Promise<CopilotMetrics[]> {\n const octokit = await this.getOrganizationOctokit();\n const path = `/orgs/${this.copilotConfig.organization}/copilot/metrics`;\n\n try {\n const response = await octokit.request(`GET ${path}`);\n return response.data as CopilotMetrics[];\n } catch (error) {\n throw ResponseError.fromResponse(error.response || error);\n }\n }\n\n async fetchOrganizationTeamCopilotMetrics(\n teamId: string,\n ): Promise<CopilotMetrics[]> {\n const octokit = await this.getOrganizationOctokit();\n const path = `/orgs/${this.copilotConfig.organization}/team/${teamId}/copilot/metrics`;\n\n try {\n const response = await octokit.request(`GET ${path}`);\n return response.data as CopilotMetrics[];\n } catch (error) {\n throw ResponseError.fromResponse(error.response || error);\n }\n }\n\n async fetchOrganizationTeams(): Promise<TeamInfo[]> {\n const octokit = await this.getOrganizationOctokit();\n\n try {\n const teams: TeamInfo[] = [];\n let cursor: string | null = null;\n let hasNextPage = true;\n\n while (hasNextPage) {\n const query = `\n query($org: String!, $cursor: String) {\n organization(login: $org) {\n teams(first: 100, after: $cursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n databaseId\n slug\n name\n members {\n totalCount\n }\n }\n }\n }\n }\n `;\n\n const variables = {\n org: this.copilotConfig.organization,\n cursor,\n };\n\n const response: any = await octokit.graphql(query, variables);\n const teamsData = response.organization.teams;\n\n // Filter teams with 5 or more members\n const filteredTeams = teamsData.nodes\n .filter((team: any) => team.members.totalCount >= 5)\n .map((team: any) => ({\n id: team.databaseId,\n slug: team.slug,\n name: team.name,\n }));\n\n teams.push(...filteredTeams);\n\n hasNextPage = teamsData.pageInfo.hasNextPage;\n cursor = teamsData.pageInfo.endCursor;\n }\n\n return teams;\n } catch (error) {\n throw ResponseError.fromResponse(error.response || error);\n }\n }\n\n async fetchOrganizationSeats(): Promise<CopilotSeats> {\n const octokit = await this.getOrganizationOctokit();\n\n try {\n const seats = await octokit.paginate(octokit.copilot.listCopilotSeats, {\n org: this.copilotConfig.organization!,\n per_page: 100, // Maximum allowed per page\n });\n\n return this.mergePaginationResult(seats as CopilotSeats[]);\n } catch (error) {\n throw ResponseError.fromResponse(error.response || error);\n }\n }\n\n /**\n * This function is used to merge paginated results from the GitHub API\n * that does not work as one would expect. If the api returns a object which\n * contains paginated results, we get an array of the objects instead of merged data.\n * So this function merges this data into one object where the property \"seats\" are\n * merged into a single array.\n * @param data\n * @returns paginated result as one would expect\n */\n mergePaginationResult(data: CopilotSeats[]): CopilotSeats {\n if (data.length === 0) {\n return {\n total_seats: 0,\n seats: [],\n };\n }\n\n // total_seats is the same for all pages, so we can just take it from the first page\n // and merge the seats from all pages into one array\n const totalSeats = data[0].total_seats;\n const seats = data.map(seat => seat.seats).flat();\n\n return {\n total_seats: totalSeats,\n seats: seats,\n };\n }\n}\n"],"names":["getCopilotConfig","getGithubCredentials","Octokit","ResponseError"],"mappings":"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"}
1
+ {"version":3,"file":"GithubClient.cjs.js","sources":["../../src/client/GithubClient.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport {\n CopilotMetrics,\n CopilotSeats,\n TeamInfo,\n} from '@backstage-community/plugin-copilot-common';\nimport { Octokit } from '@octokit/rest';\nimport { createAppAuth } from '@octokit/auth-app';\nimport {\n CopilotConfig,\n CopilotCredentials,\n getCopilotConfig,\n getGithubCredentials,\n} from '../utils/GithubUtils';\n\ninterface GithubApi {\n fetchEnterpriseCopilotMetrics: () => Promise<CopilotMetrics[]>;\n fetchEnterpriseTeamCopilotMetrics: (\n teamId: string,\n ) => Promise<CopilotMetrics[]>;\n fetchOrganizationCopilotMetrics: () => Promise<CopilotMetrics[]>;\n fetchOrganizationTeamCopilotMetrics: (\n teamId: string,\n ) => Promise<CopilotMetrics[]>;\n\n fetchEnterpriseTeams: () => Promise<TeamInfo[]>;\n fetchOrganizationTeams: () => Promise<TeamInfo[]>;\n}\n\nexport class GithubClient implements GithubApi {\n private enterpriseOctokit?: Octokit;\n private organizationOctokit?: Octokit;\n\n constructor(\n private readonly copilotConfig: CopilotConfig,\n private readonly config: Config,\n private readonly logger: LoggerService,\n ) {}\n\n static async fromConfig(config: Config, logger: LoggerService) {\n const info = getCopilotConfig(config);\n return new GithubClient(info, config, logger);\n }\n\n private async getCredentials(): Promise<CopilotCredentials> {\n return await getGithubCredentials(this.config, this.copilotConfig);\n }\n\n private async getOctokit(\n type: 'enterprise' | 'organization',\n ): Promise<Octokit> {\n const credentials = await this.getCredentials();\n const authStrategy = credentials[type];\n\n if (!authStrategy) {\n throw new Error(\n `No credentials configured for ${type}. Please configure GitHub integration for ${\n type === 'organization'\n ? this.copilotConfig.organization\n : this.copilotConfig.enterprise\n } in your app-config.`,\n );\n }\n\n const octokitConfig: ConstructorParameters<typeof Octokit>[0] = {\n baseUrl: this.copilotConfig.apiBaseUrl,\n headers: {\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n },\n };\n\n // If it's a string token, use it directly\n // If it's an auth strategy config (GitHub App), pass authStrategy and auth\n if (typeof authStrategy === 'string') {\n this.logger.debug(\n `[GithubClient] Using token authentication for ${type}`,\n );\n octokitConfig.auth = authStrategy;\n } else {\n this.logger.debug(\n `[GithubClient] Using GitHub App authentication for ${type}`,\n {\n appId: authStrategy.appId,\n },\n );\n\n // For GitHub Apps, we need to get the installation ID first\n // Create a temporary app-authenticated Octokit to get the installation ID\n const appOctokit = new Octokit({\n authStrategy: createAppAuth,\n auth: {\n appId: authStrategy.appId,\n privateKey: authStrategy.privateKey,\n },\n });\n\n // Get installation ID for the organization\n const orgName =\n type === 'organization'\n ? this.copilotConfig.organization\n : this.copilotConfig.enterprise;\n\n if (!orgName) {\n throw new Error(\n `No ${type} name configured in your app-config. Please see the documentation for setup instructions.`,\n );\n }\n\n const { data: installation } =\n await appOctokit.rest.apps.getOrgInstallation({\n org: orgName,\n });\n\n this.logger.debug(\n `[GithubClient] Got installation ID ${installation.id} for ${orgName}`,\n );\n\n // Now create the properly configured auth\n octokitConfig.authStrategy = createAppAuth;\n octokitConfig.auth = {\n appId: authStrategy.appId,\n privateKey: authStrategy.privateKey,\n installationId: installation.id,\n };\n }\n\n return new Octokit(octokitConfig);\n }\n\n private async getEnterpriseOctokit(): Promise<Octokit> {\n if (!this.enterpriseOctokit) {\n this.enterpriseOctokit = await this.getOctokit('enterprise');\n }\n return this.enterpriseOctokit;\n }\n\n private async getOrganizationOctokit(): Promise<Octokit> {\n if (!this.organizationOctokit) {\n this.organizationOctokit = await this.getOctokit('organization');\n }\n return this.organizationOctokit;\n }\n\n async fetchEnterpriseCopilotMetrics(): Promise<CopilotMetrics[]> {\n const octokit = await this.getEnterpriseOctokit();\n const path = `/enterprises/${this.copilotConfig.enterprise}/copilot/metrics`;\n\n const response = await octokit.request(`GET ${path}`);\n return response.data as CopilotMetrics[];\n }\n\n async fetchEnterpriseTeamCopilotMetrics(\n teamId: string,\n ): Promise<CopilotMetrics[]> {\n const octokit = await this.getEnterpriseOctokit();\n const path = `/enterprises/${this.copilotConfig.enterprise}/team/${teamId}/copilot/metrics`;\n\n const response = await octokit.request(`GET ${path}`);\n return response.data as CopilotMetrics[];\n }\n\n async fetchEnterpriseTeams(): Promise<TeamInfo[]> {\n const octokit = await this.getEnterpriseOctokit();\n\n const teams: TeamInfo[] = [];\n let cursor: string | null = null;\n let hasNextPage = true;\n\n while (hasNextPage) {\n const query = `\n query($enterprise: String!, $cursor: String) {\n enterprise(slug: $enterprise) {\n organizations(first: 100) {\n nodes {\n teams(first: 100, after: $cursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n databaseId\n slug\n name\n members {\n totalCount\n }\n }\n }\n }\n }\n }\n }\n `;\n\n const variables = {\n enterprise: this.copilotConfig.enterprise,\n cursor,\n };\n\n const response: any = await octokit.graphql(query, variables);\n\n // Flatten teams from all organizations in the enterprise\n const allTeams = response.enterprise.organizations.nodes.flatMap(\n (org: any) => org.teams.nodes,\n );\n\n // Filter teams with 5 or more members\n const filteredTeams = allTeams\n .filter((team: any) => team.members.totalCount >= 5)\n .map((team: any) => ({\n id: team.databaseId,\n slug: team.slug,\n name: team.name,\n }));\n\n teams.push(...filteredTeams);\n\n // Check if any organization has more teams to fetch\n hasNextPage = response.enterprise.organizations.nodes.some(\n (org: any) => org.teams.pageInfo.hasNextPage,\n );\n\n if (hasNextPage) {\n cursor = response.enterprise.organizations.nodes.find(\n (org: any) => org.teams.pageInfo.hasNextPage,\n )?.teams.pageInfo.endCursor;\n }\n }\n\n return teams;\n }\n\n async fetchEnterpriseSeats(): Promise<any> {\n const octokit = await this.getEnterpriseOctokit();\n const path = `/enterprises/${this.copilotConfig.enterprise}/copilot/billing/seats`;\n\n const seats = await octokit.paginate(`GET ${path}`, {\n per_page: 100, // Maximum allowed per page\n });\n\n return this.mergePaginationResult(seats as CopilotSeats[]);\n }\n\n async fetchOrganizationCopilotMetrics(): Promise<CopilotMetrics[]> {\n const octokit = await this.getOrganizationOctokit();\n const path = `/orgs/${this.copilotConfig.organization}/copilot/metrics`;\n\n const response = await octokit.request(`GET ${path}`);\n return response.data as CopilotMetrics[];\n }\n\n async fetchOrganizationTeamCopilotMetrics(\n teamId: string,\n ): Promise<CopilotMetrics[]> {\n const octokit = await this.getOrganizationOctokit();\n const path = `/orgs/${this.copilotConfig.organization}/team/${teamId}/copilot/metrics`;\n\n const response = await octokit.request(`GET ${path}`);\n return response.data as CopilotMetrics[];\n }\n\n async fetchOrganizationTeams(): Promise<TeamInfo[]> {\n const octokit = await this.getOrganizationOctokit();\n\n const teams: TeamInfo[] = [];\n let cursor: string | null = null;\n let hasNextPage = true;\n\n while (hasNextPage) {\n const query = `\n query($org: String!, $cursor: String) {\n organization(login: $org) {\n teams(first: 100, after: $cursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n databaseId\n slug\n name\n members {\n totalCount\n }\n }\n }\n }\n }\n `;\n\n const variables = {\n org: this.copilotConfig.organization,\n cursor,\n };\n\n const response: any = await octokit.graphql(query, variables);\n const teamsData = response.organization.teams;\n\n // Filter teams with 5 or more members\n const filteredTeams = teamsData.nodes\n .filter((team: any) => team.members.totalCount >= 5)\n .map((team: any) => ({\n id: team.databaseId,\n slug: team.slug,\n name: team.name,\n }));\n\n teams.push(...filteredTeams);\n\n hasNextPage = teamsData.pageInfo.hasNextPage;\n cursor = teamsData.pageInfo.endCursor;\n }\n\n return teams;\n }\n\n async fetchOrganizationSeats(): Promise<CopilotSeats> {\n const octokit = await this.getOrganizationOctokit();\n\n const seats = await octokit.paginate(octokit.copilot.listCopilotSeats, {\n org: this.copilotConfig.organization!,\n per_page: 100, // Maximum allowed per page\n });\n\n return this.mergePaginationResult(seats as CopilotSeats[]);\n }\n\n /**\n * This function is used to merge paginated results from the GitHub API\n * that does not work as one would expect. If the api returns a object which\n * contains paginated results, we get an array of the objects instead of merged data.\n * So this function merges this data into one object where the property \"seats\" are\n * merged into a single array.\n * @param data\n * @returns paginated result as one would expect\n */\n mergePaginationResult(data: CopilotSeats[]): CopilotSeats {\n if (data.length === 0) {\n return {\n total_seats: 0,\n seats: [],\n };\n }\n\n // total_seats is the same for all pages, so we can just take it from the first page\n // and merge the seats from all pages into one array\n const totalSeats = data[0].total_seats;\n const seats = data.map(seat => seat.seats).flat();\n\n return {\n total_seats: totalSeats,\n seats: seats,\n };\n }\n}\n"],"names":["getCopilotConfig","getGithubCredentials","Octokit","createAppAuth"],"mappings":"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"}
@@ -39,7 +39,7 @@ async function createRouter(routerOptions, pluginOptions) {
39
39
  const { logger, database, scheduler, config } = routerOptions;
40
40
  const { schedule } = pluginOptions;
41
41
  const db = await DatabaseHandler.DatabaseHandler.create({ database });
42
- const api = await GithubClient.GithubClient.fromConfig(config);
42
+ const api = await GithubClient.GithubClient.fromConfig(config, logger);
43
43
  await scheduler.scheduleTask({
44
44
  id: "copilot-metrics",
45
45
  ...schedule ?? defaultSchedule,
@@ -1 +1 @@
1
- {"version":3,"file":"router.cjs.js","sources":["../../src/service/router.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter';\nimport {\n DatabaseService,\n LoggerService,\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n SchedulerService,\n SchedulerServiceTaskScheduleDefinition,\n} from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { NotFoundError } from '@backstage/errors';\nimport {\n Metric,\n PeriodRange,\n} from '@backstage-community/plugin-copilot-common';\nimport { DatabaseHandler } from '../db/DatabaseHandler';\nimport TaskManagement from '../task/TaskManagement';\nimport { GithubClient } from '../client/GithubClient';\nimport { validateQuery } from './validation/validateQuery';\nimport {\n MetricsQuery,\n metricsQuerySchema,\n PeriodRangeQuery,\n periodRangeQuerySchema,\n TeamQuery,\n teamQuerySchema,\n} from './validation/schema';\nimport { DateTime } from 'luxon';\n\n/**\n * Options for configuring the Copilot plugin.\n *\n * @public\n */\nexport interface PluginOptions {\n /**\n * Schedule configuration for the plugin.\n */\n schedule?: SchedulerServiceTaskScheduleDefinition;\n}\n\n/**\n * Options for configuring the router used by the Copilot plugin.\n *\n * @public\n */\nexport interface RouterOptions {\n /**\n * Logger service for the router.\n */\n logger: LoggerService;\n\n /**\n * Database service for the router.\n */\n database: DatabaseService;\n\n /**\n * Scheduler service for the router.\n */\n scheduler: SchedulerService;\n\n /**\n * Configuration for the router.\n */\n config: Config;\n}\n\nconst defaultSchedule: SchedulerServiceTaskScheduleDefinition = {\n frequency: { cron: '0 2 * * *' },\n timeout: { minutes: 15 },\n initialDelay: { minutes: 1 },\n scope: 'local',\n};\n\n/**\n * Creates an Express router configured based on the provided router options and plugin options.\n *\n * This function initializes the router with the appropriate middleware and routes based on the\n * configuration and options provided. It also schedules tasks if scheduling options are provided.\n *\n * @param routerOptions - Options for configuring the router, including services and configuration.\n * @returns A promise that resolves to an Express router instance.\n *\n * @public\n */\nexport async function createRouterFromConfig(routerOptions: RouterOptions) {\n const { config } = routerOptions;\n const pluginOptions: PluginOptions = {\n schedule: defaultSchedule,\n };\n if (config && config.has('copilot.schedule')) {\n pluginOptions.schedule =\n readSchedulerServiceTaskScheduleDefinitionFromConfig(\n config.getConfig('copilot.schedule'),\n );\n }\n return createRouter(routerOptions, pluginOptions);\n}\n\n/** @private */\nasync function createRouter(\n routerOptions: RouterOptions,\n pluginOptions: PluginOptions,\n): Promise<express.Router> {\n const { logger, database, scheduler, config } = routerOptions;\n const { schedule } = pluginOptions;\n\n const db = await DatabaseHandler.create({ database });\n const api = await GithubClient.fromConfig(config);\n\n await scheduler.scheduleTask({\n id: 'copilot-metrics',\n ...(schedule ?? defaultSchedule),\n fn: async () =>\n await TaskManagement.create({ db, logger, api, config }).runAsync(),\n });\n\n const router = Router();\n router.use(express.json());\n\n router.get('/health', (_, response) => {\n logger.info('PONG!');\n response.json({ status: 'ok' });\n });\n\n router.get(\n '/metrics',\n validateQuery(metricsQuerySchema),\n async (req, res) => {\n const { startDate, endDate, type, team } = req.query as MetricsQuery;\n let metrics: Metric[] = [];\n\n // if startDate is earlier than last date of MetricsV1, fetch the old data\n const lastDayOfOldMetrics = await db.getMostRecentDayFromMetrics(\n type,\n team,\n );\n\n if (\n startDate &&\n lastDayOfOldMetrics &&\n DateTime.fromISO(startDate) <=\n DateTime.fromJSDate(new Date(lastDayOfOldMetrics))\n ) {\n const result = await db.getMetrics(startDate, endDate, type, team);\n metrics = result.map(metric => ({\n ...metric,\n breakdown: JSON.parse(metric.breakdown),\n }));\n }\n\n // if endDate is later or equal to first day of new metrics, fetch those also and merge into metrics\n const firstDayOfNewMetrics = await db.getEarliestDayFromMetricsV2(\n type,\n team,\n );\n if (\n endDate &&\n firstDayOfNewMetrics &&\n DateTime.fromISO(endDate) >=\n DateTime.fromJSDate(new Date(firstDayOfNewMetrics))\n ) {\n const result = await db.getMetricsV2(startDate, endDate, type, team);\n const breakdown = await db.getBreakdown(startDate, endDate, type, team);\n\n const newMetrics = result.map(metric => ({\n ...metric,\n breakdown: breakdown.filter(day => {\n const metricDate = DateTime.fromJSDate(new Date(metric.day));\n const dayDate = DateTime.fromJSDate(new Date(day.day));\n return metricDate.equals(dayDate);\n }),\n }));\n\n // Merge new metrics with old metrics\n metrics = [...metrics, ...newMetrics];\n }\n\n return res.json(metrics);\n },\n );\n\n router.get(\n '/engagements',\n validateQuery(metricsQuerySchema),\n async (req, res) => {\n const { startDate, endDate, type, team } = req.query as MetricsQuery;\n\n const engagements = await db.getEngagementMetrics(\n startDate,\n endDate,\n type,\n team,\n );\n if (!engagements) {\n throw new NotFoundError();\n }\n\n return res.json(engagements);\n },\n );\n\n router.get('/seats', validateQuery(metricsQuerySchema), async (req, res) => {\n const { startDate, endDate, type, team } = req.query as MetricsQuery;\n\n const seats = await db.getSeatMetrics(startDate, endDate, type, team);\n if (!seats) {\n throw new NotFoundError();\n }\n\n return res.json(seats);\n });\n\n router.get(\n '/metrics/period-range',\n validateQuery(periodRangeQuerySchema),\n async (req, res) => {\n const { type } = req.query as PeriodRangeQuery;\n const oldMetricRange = await db.getPeriodRange(type);\n const newMetricRange = await db.getPeriodRangeV2(type);\n\n if (!oldMetricRange && !newMetricRange) {\n throw new NotFoundError();\n }\n\n // Determine the minDate, prioritizing oldMetricRange if available\n const minDate = oldMetricRange?.minDate || newMetricRange?.minDate;\n\n // Determine the maxDate, prioritizing newMetricRange if available\n const maxDate = newMetricRange?.maxDate || oldMetricRange?.maxDate;\n\n // Make sure both minDate and maxDate are defined\n if (!minDate || !maxDate) {\n throw new NotFoundError('Unable to determine metric date range');\n }\n\n const result: PeriodRange = { minDate, maxDate };\n\n return res.json(result);\n },\n );\n\n router.get('/teams', validateQuery(teamQuerySchema), async (req, res) => {\n const { type, startDate, endDate } = req.query as TeamQuery;\n\n const result = await db.getTeams(type, startDate, endDate);\n\n if (!result) {\n throw new NotFoundError();\n }\n\n return res.json(result);\n });\n\n router.use(MiddlewareFactory.create({ config, logger }).error);\n return router;\n}\n"],"names":["readSchedulerServiceTaskScheduleDefinitionFromConfig","DatabaseHandler","GithubClient","TaskManagement","Router","express","validateQuery","metricsQuerySchema","DateTime","NotFoundError","periodRangeQuerySchema","teamQuerySchema","MiddlewareFactory"],"mappings":"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"}
1
+ {"version":3,"file":"router.cjs.js","sources":["../../src/service/router.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter';\nimport {\n DatabaseService,\n LoggerService,\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n SchedulerService,\n SchedulerServiceTaskScheduleDefinition,\n} from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { NotFoundError } from '@backstage/errors';\nimport {\n Metric,\n PeriodRange,\n} from '@backstage-community/plugin-copilot-common';\nimport { DatabaseHandler } from '../db/DatabaseHandler';\nimport TaskManagement from '../task/TaskManagement';\nimport { GithubClient } from '../client/GithubClient';\nimport { validateQuery } from './validation/validateQuery';\nimport {\n MetricsQuery,\n metricsQuerySchema,\n PeriodRangeQuery,\n periodRangeQuerySchema,\n TeamQuery,\n teamQuerySchema,\n} from './validation/schema';\nimport { DateTime } from 'luxon';\n\n/**\n * Options for configuring the Copilot plugin.\n *\n * @public\n */\nexport interface PluginOptions {\n /**\n * Schedule configuration for the plugin.\n */\n schedule?: SchedulerServiceTaskScheduleDefinition;\n}\n\n/**\n * Options for configuring the router used by the Copilot plugin.\n *\n * @public\n */\nexport interface RouterOptions {\n /**\n * Logger service for the router.\n */\n logger: LoggerService;\n\n /**\n * Database service for the router.\n */\n database: DatabaseService;\n\n /**\n * Scheduler service for the router.\n */\n scheduler: SchedulerService;\n\n /**\n * Configuration for the router.\n */\n config: Config;\n}\n\nconst defaultSchedule: SchedulerServiceTaskScheduleDefinition = {\n frequency: { cron: '0 2 * * *' },\n timeout: { minutes: 15 },\n initialDelay: { minutes: 1 },\n scope: 'local',\n};\n\n/**\n * Creates an Express router configured based on the provided router options and plugin options.\n *\n * This function initializes the router with the appropriate middleware and routes based on the\n * configuration and options provided. It also schedules tasks if scheduling options are provided.\n *\n * @param routerOptions - Options for configuring the router, including services and configuration.\n * @returns A promise that resolves to an Express router instance.\n *\n * @public\n */\nexport async function createRouterFromConfig(routerOptions: RouterOptions) {\n const { config } = routerOptions;\n const pluginOptions: PluginOptions = {\n schedule: defaultSchedule,\n };\n if (config && config.has('copilot.schedule')) {\n pluginOptions.schedule =\n readSchedulerServiceTaskScheduleDefinitionFromConfig(\n config.getConfig('copilot.schedule'),\n );\n }\n return createRouter(routerOptions, pluginOptions);\n}\n\n/** @private */\nasync function createRouter(\n routerOptions: RouterOptions,\n pluginOptions: PluginOptions,\n): Promise<express.Router> {\n const { logger, database, scheduler, config } = routerOptions;\n const { schedule } = pluginOptions;\n\n const db = await DatabaseHandler.create({ database });\n const api = await GithubClient.fromConfig(config, logger);\n\n await scheduler.scheduleTask({\n id: 'copilot-metrics',\n ...(schedule ?? defaultSchedule),\n fn: async () =>\n await TaskManagement.create({ db, logger, api, config }).runAsync(),\n });\n\n const router = Router();\n router.use(express.json());\n\n router.get('/health', (_, response) => {\n logger.info('PONG!');\n response.json({ status: 'ok' });\n });\n\n router.get(\n '/metrics',\n validateQuery(metricsQuerySchema),\n async (req, res) => {\n const { startDate, endDate, type, team } = req.query as MetricsQuery;\n let metrics: Metric[] = [];\n\n // if startDate is earlier than last date of MetricsV1, fetch the old data\n const lastDayOfOldMetrics = await db.getMostRecentDayFromMetrics(\n type,\n team,\n );\n\n if (\n startDate &&\n lastDayOfOldMetrics &&\n DateTime.fromISO(startDate) <=\n DateTime.fromJSDate(new Date(lastDayOfOldMetrics))\n ) {\n const result = await db.getMetrics(startDate, endDate, type, team);\n metrics = result.map(metric => ({\n ...metric,\n breakdown: JSON.parse(metric.breakdown),\n }));\n }\n\n // if endDate is later or equal to first day of new metrics, fetch those also and merge into metrics\n const firstDayOfNewMetrics = await db.getEarliestDayFromMetricsV2(\n type,\n team,\n );\n if (\n endDate &&\n firstDayOfNewMetrics &&\n DateTime.fromISO(endDate) >=\n DateTime.fromJSDate(new Date(firstDayOfNewMetrics))\n ) {\n const result = await db.getMetricsV2(startDate, endDate, type, team);\n const breakdown = await db.getBreakdown(startDate, endDate, type, team);\n\n const newMetrics = result.map(metric => ({\n ...metric,\n breakdown: breakdown.filter(day => {\n const metricDate = DateTime.fromJSDate(new Date(metric.day));\n const dayDate = DateTime.fromJSDate(new Date(day.day));\n return metricDate.equals(dayDate);\n }),\n }));\n\n // Merge new metrics with old metrics\n metrics = [...metrics, ...newMetrics];\n }\n\n return res.json(metrics);\n },\n );\n\n router.get(\n '/engagements',\n validateQuery(metricsQuerySchema),\n async (req, res) => {\n const { startDate, endDate, type, team } = req.query as MetricsQuery;\n\n const engagements = await db.getEngagementMetrics(\n startDate,\n endDate,\n type,\n team,\n );\n if (!engagements) {\n throw new NotFoundError();\n }\n\n return res.json(engagements);\n },\n );\n\n router.get('/seats', validateQuery(metricsQuerySchema), async (req, res) => {\n const { startDate, endDate, type, team } = req.query as MetricsQuery;\n\n const seats = await db.getSeatMetrics(startDate, endDate, type, team);\n if (!seats) {\n throw new NotFoundError();\n }\n\n return res.json(seats);\n });\n\n router.get(\n '/metrics/period-range',\n validateQuery(periodRangeQuerySchema),\n async (req, res) => {\n const { type } = req.query as PeriodRangeQuery;\n const oldMetricRange = await db.getPeriodRange(type);\n const newMetricRange = await db.getPeriodRangeV2(type);\n\n if (!oldMetricRange && !newMetricRange) {\n throw new NotFoundError();\n }\n\n // Determine the minDate, prioritizing oldMetricRange if available\n const minDate = oldMetricRange?.minDate || newMetricRange?.minDate;\n\n // Determine the maxDate, prioritizing newMetricRange if available\n const maxDate = newMetricRange?.maxDate || oldMetricRange?.maxDate;\n\n // Make sure both minDate and maxDate are defined\n if (!minDate || !maxDate) {\n throw new NotFoundError('Unable to determine metric date range');\n }\n\n const result: PeriodRange = { minDate, maxDate };\n\n return res.json(result);\n },\n );\n\n router.get('/teams', validateQuery(teamQuerySchema), async (req, res) => {\n const { type, startDate, endDate } = req.query as TeamQuery;\n\n const result = await db.getTeams(type, startDate, endDate);\n\n if (!result) {\n throw new NotFoundError();\n }\n\n return res.json(result);\n });\n\n router.use(MiddlewareFactory.create({ config, logger }).error);\n return router;\n}\n"],"names":["readSchedulerServiceTaskScheduleDefinitionFromConfig","DatabaseHandler","GithubClient","TaskManagement","Router","express","validateQuery","metricsQuerySchema","DateTime","NotFoundError","periodRangeQuerySchema","teamQuerySchema","MiddlewareFactory"],"mappings":";;;;;;;;;;;;;;;;;;;AAoFA,MAAM,eAA0D,GAAA;AAAA,EAC9D,SAAA,EAAW,EAAE,IAAA,EAAM,WAAY,EAAA;AAAA,EAC/B,OAAA,EAAS,EAAE,OAAA,EAAS,EAAG,EAAA;AAAA,EACvB,YAAA,EAAc,EAAE,OAAA,EAAS,CAAE,EAAA;AAAA,EAC3B,KAAO,EAAA;AACT,CAAA;AAaA,eAAsB,uBAAuB,aAA8B,EAAA;AACzE,EAAM,MAAA,EAAE,QAAW,GAAA,aAAA;AACnB,EAAA,MAAM,aAA+B,GAAA;AAAA,IACnC,QAAU,EAAA;AAAA,GACZ;AACA,EAAA,IAAI,MAAU,IAAA,MAAA,CAAO,GAAI,CAAA,kBAAkB,CAAG,EAAA;AAC5C,IAAA,aAAA,CAAc,QACZ,GAAAA,qEAAA;AAAA,MACE,MAAA,CAAO,UAAU,kBAAkB;AAAA,KACrC;AAAA;AAEJ,EAAO,OAAA,YAAA,CAAa,eAAe,aAAa,CAAA;AAClD;AAGA,eAAe,YAAA,CACb,eACA,aACyB,EAAA;AACzB,EAAA,MAAM,EAAE,MAAA,EAAQ,QAAU,EAAA,SAAA,EAAW,QAAW,GAAA,aAAA;AAChD,EAAM,MAAA,EAAE,UAAa,GAAA,aAAA;AAErB,EAAA,MAAM,KAAK,MAAMC,+BAAA,CAAgB,MAAO,CAAA,EAAE,UAAU,CAAA;AACpD,EAAA,MAAM,GAAM,GAAA,MAAMC,yBAAa,CAAA,UAAA,CAAW,QAAQ,MAAM,CAAA;AAExD,EAAA,MAAM,UAAU,YAAa,CAAA;AAAA,IAC3B,EAAI,EAAA,iBAAA;AAAA,IACJ,GAAI,QAAY,IAAA,eAAA;AAAA,IAChB,EAAI,EAAA,YACF,MAAMC,sBAAA,CAAe,MAAO,CAAA,EAAE,EAAI,EAAA,MAAA,EAAQ,GAAK,EAAA,MAAA,EAAQ,CAAA,CAAE,QAAS;AAAA,GACrE,CAAA;AAED,EAAA,MAAM,SAASC,uBAAO,EAAA;AACtB,EAAO,MAAA,CAAA,GAAA,CAAIC,wBAAQ,CAAA,IAAA,EAAM,CAAA;AAEzB,EAAA,MAAA,CAAO,GAAI,CAAA,SAAA,EAAW,CAAC,CAAA,EAAG,QAAa,KAAA;AACrC,IAAA,MAAA,CAAO,KAAK,OAAO,CAAA;AACnB,IAAA,QAAA,CAAS,IAAK,CAAA,EAAE,MAAQ,EAAA,IAAA,EAAM,CAAA;AAAA,GAC/B,CAAA;AAED,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,UAAA;AAAA,IACAC,4BAAcC,yBAAkB,CAAA;AAAA,IAChC,OAAO,KAAK,GAAQ,KAAA;AAClB,MAAA,MAAM,EAAE,SAAW,EAAA,OAAA,EAAS,IAAM,EAAA,IAAA,KAAS,GAAI,CAAA,KAAA;AAC/C,MAAA,IAAI,UAAoB,EAAC;AAGzB,MAAM,MAAA,mBAAA,GAAsB,MAAM,EAAG,CAAA,2BAAA;AAAA,QACnC,IAAA;AAAA,QACA;AAAA,OACF;AAEA,MAAA,IACE,SACA,IAAA,mBAAA,IACAC,cAAS,CAAA,OAAA,CAAQ,SAAS,CAAA,IACxBA,cAAS,CAAA,UAAA,CAAW,IAAI,IAAA,CAAK,mBAAmB,CAAC,CACnD,EAAA;AACA,QAAA,MAAM,SAAS,MAAM,EAAA,CAAG,WAAW,SAAW,EAAA,OAAA,EAAS,MAAM,IAAI,CAAA;AACjE,QAAU,OAAA,GAAA,MAAA,CAAO,IAAI,CAAW,MAAA,MAAA;AAAA,UAC9B,GAAG,MAAA;AAAA,UACH,SAAW,EAAA,IAAA,CAAK,KAAM,CAAA,MAAA,CAAO,SAAS;AAAA,SACtC,CAAA,CAAA;AAAA;AAIJ,MAAM,MAAA,oBAAA,GAAuB,MAAM,EAAG,CAAA,2BAAA;AAAA,QACpC,IAAA;AAAA,QACA;AAAA,OACF;AACA,MAAA,IACE,OACA,IAAA,oBAAA,IACAA,cAAS,CAAA,OAAA,CAAQ,OAAO,CAAA,IACtBA,cAAS,CAAA,UAAA,CAAW,IAAI,IAAA,CAAK,oBAAoB,CAAC,CACpD,EAAA;AACA,QAAA,MAAM,SAAS,MAAM,EAAA,CAAG,aAAa,SAAW,EAAA,OAAA,EAAS,MAAM,IAAI,CAAA;AACnE,QAAA,MAAM,YAAY,MAAM,EAAA,CAAG,aAAa,SAAW,EAAA,OAAA,EAAS,MAAM,IAAI,CAAA;AAEtE,QAAM,MAAA,UAAA,GAAa,MAAO,CAAA,GAAA,CAAI,CAAW,MAAA,MAAA;AAAA,UACvC,GAAG,MAAA;AAAA,UACH,SAAA,EAAW,SAAU,CAAA,MAAA,CAAO,CAAO,GAAA,KAAA;AACjC,YAAA,MAAM,aAAaA,cAAS,CAAA,UAAA,CAAW,IAAI,IAAK,CAAA,MAAA,CAAO,GAAG,CAAC,CAAA;AAC3D,YAAA,MAAM,UAAUA,cAAS,CAAA,UAAA,CAAW,IAAI,IAAK,CAAA,GAAA,CAAI,GAAG,CAAC,CAAA;AACrD,YAAO,OAAA,UAAA,CAAW,OAAO,OAAO,CAAA;AAAA,WACjC;AAAA,SACD,CAAA,CAAA;AAGF,QAAA,OAAA,GAAU,CAAC,GAAG,OAAS,EAAA,GAAG,UAAU,CAAA;AAAA;AAGtC,MAAO,OAAA,GAAA,CAAI,KAAK,OAAO,CAAA;AAAA;AACzB,GACF;AAEA,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,cAAA;AAAA,IACAF,4BAAcC,yBAAkB,CAAA;AAAA,IAChC,OAAO,KAAK,GAAQ,KAAA;AAClB,MAAA,MAAM,EAAE,SAAW,EAAA,OAAA,EAAS,IAAM,EAAA,IAAA,KAAS,GAAI,CAAA,KAAA;AAE/C,MAAM,MAAA,WAAA,GAAc,MAAM,EAAG,CAAA,oBAAA;AAAA,QAC3B,SAAA;AAAA,QACA,OAAA;AAAA,QACA,IAAA;AAAA,QACA;AAAA,OACF;AACA,MAAA,IAAI,CAAC,WAAa,EAAA;AAChB,QAAA,MAAM,IAAIE,oBAAc,EAAA;AAAA;AAG1B,MAAO,OAAA,GAAA,CAAI,KAAK,WAAW,CAAA;AAAA;AAC7B,GACF;AAEA,EAAA,MAAA,CAAO,IAAI,QAAU,EAAAH,2BAAA,CAAcC,yBAAkB,CAAG,EAAA,OAAO,KAAK,GAAQ,KAAA;AAC1E,IAAA,MAAM,EAAE,SAAW,EAAA,OAAA,EAAS,IAAM,EAAA,IAAA,KAAS,GAAI,CAAA,KAAA;AAE/C,IAAA,MAAM,QAAQ,MAAM,EAAA,CAAG,eAAe,SAAW,EAAA,OAAA,EAAS,MAAM,IAAI,CAAA;AACpE,IAAA,IAAI,CAAC,KAAO,EAAA;AACV,MAAA,MAAM,IAAIE,oBAAc,EAAA;AAAA;AAG1B,IAAO,OAAA,GAAA,CAAI,KAAK,KAAK,CAAA;AAAA,GACtB,CAAA;AAED,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,uBAAA;AAAA,IACAH,4BAAcI,6BAAsB,CAAA;AAAA,IACpC,OAAO,KAAK,GAAQ,KAAA;AAClB,MAAM,MAAA,EAAE,IAAK,EAAA,GAAI,GAAI,CAAA,KAAA;AACrB,MAAA,MAAM,cAAiB,GAAA,MAAM,EAAG,CAAA,cAAA,CAAe,IAAI,CAAA;AACnD,MAAA,MAAM,cAAiB,GAAA,MAAM,EAAG,CAAA,gBAAA,CAAiB,IAAI,CAAA;AAErD,MAAI,IAAA,CAAC,cAAkB,IAAA,CAAC,cAAgB,EAAA;AACtC,QAAA,MAAM,IAAID,oBAAc,EAAA;AAAA;AAI1B,MAAM,MAAA,OAAA,GAAU,cAAgB,EAAA,OAAA,IAAW,cAAgB,EAAA,OAAA;AAG3D,MAAM,MAAA,OAAA,GAAU,cAAgB,EAAA,OAAA,IAAW,cAAgB,EAAA,OAAA;AAG3D,MAAI,IAAA,CAAC,OAAW,IAAA,CAAC,OAAS,EAAA;AACxB,QAAM,MAAA,IAAIA,qBAAc,uCAAuC,CAAA;AAAA;AAGjE,MAAM,MAAA,MAAA,GAAsB,EAAE,OAAA,EAAS,OAAQ,EAAA;AAE/C,MAAO,OAAA,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA;AACxB,GACF;AAEA,EAAA,MAAA,CAAO,IAAI,QAAU,EAAAH,2BAAA,CAAcK,sBAAe,CAAG,EAAA,OAAO,KAAK,GAAQ,KAAA;AACvE,IAAA,MAAM,EAAE,IAAA,EAAM,SAAW,EAAA,OAAA,KAAY,GAAI,CAAA,KAAA;AAEzC,IAAA,MAAM,SAAS,MAAM,EAAA,CAAG,QAAS,CAAA,IAAA,EAAM,WAAW,OAAO,CAAA;AAEzD,IAAA,IAAI,CAAC,MAAQ,EAAA;AACX,MAAA,MAAM,IAAIF,oBAAc,EAAA;AAAA;AAG1B,IAAO,OAAA,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA,GACvB,CAAA;AAED,EAAO,MAAA,CAAA,GAAA,CAAIG,iCAAkB,MAAO,CAAA,EAAE,QAAQ,MAAO,EAAC,EAAE,KAAK,CAAA;AAC7D,EAAO,OAAA,MAAA;AACT;;;;"}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var batchInsert = require('../utils/batchInsert.cjs.js');
4
+ var dateUtils = require('../utils/dateUtils.cjs.js');
4
5
  var metricHelpers = require('../utils/metricHelpers.cjs.js');
5
6
 
6
7
  async function discoverEnterpriseMetrics({
@@ -22,7 +23,9 @@ async function discoverEnterpriseMetrics({
22
23
  `[discoverEnterpriseMetrics] Fetched ${copilotMetrics.length} metrics`
23
24
  );
24
25
  const lastDay = await db.getMostRecentDayFromMetricsV2(type);
25
- logger.info(`[discoverEnterpriseMetrics] Found last day: ${lastDay}`);
26
+ logger.info(
27
+ `[discoverEnterpriseMetrics] Found last day: ${dateUtils.formatDate(lastDay)}`
28
+ );
26
29
  const newMetrics = metricHelpers.filterNewMetricsV2(
27
30
  copilotMetrics,
28
31
  lastDay
@@ -103,7 +106,7 @@ async function discoverEnterpriseMetrics({
103
106
  }
104
107
  } catch (error) {
105
108
  logger.error(
106
- "[discoverEnterpriseMetrics] An error occurred while processing Github Copilot metrics",
109
+ `[discoverEnterpriseMetrics] Failed to process Github Copilot metrics.`,
107
110
  error
108
111
  );
109
112
  throw error;
@@ -1 +1 @@
1
- {"version":3,"file":"EnterpriseTask.cjs.js","sources":["../../src/task/EnterpriseTask.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n CopilotMetrics,\n MetricsType,\n} from '@backstage-community/plugin-copilot-common';\nimport { batchInsertInChunks } from '../utils/batchInsert';\nimport {\n convertToSeatAnalysis,\n filterBaseMetrics,\n filterIdeChatEditorModelMetrics,\n filterIdeChatMetrics,\n filterIdeCompletionEditorMetrics,\n filterIdeCompletionEditorModelLanguageMetrics,\n filterIdeCompletionEditorModelMetrics,\n filterIdeCompletionLanguageMetrics,\n filterIdeCompletionMetrics,\n filterIdeEditorMetrics,\n filterNewMetricsV2,\n} from '../utils/metricHelpers';\nimport { TaskOptions } from './TaskManagement';\n\nexport async function discoverEnterpriseMetrics({\n api,\n logger,\n db,\n config,\n}: TaskOptions): Promise<void> {\n if (!config.getOptionalString('copilot.enterprise')) {\n logger.info(\n '[discoverEnterpriseMetrics] Skipping: Enterprise configuration not found.',\n );\n return;\n }\n\n const type: MetricsType = 'enterprise';\n\n try {\n const copilotMetrics = await api.fetchEnterpriseCopilotMetrics();\n logger.info(\n `[discoverEnterpriseMetrics] Fetched ${copilotMetrics.length} metrics`,\n );\n\n const lastDay = await db.getMostRecentDayFromMetricsV2(type);\n logger.info(`[discoverEnterpriseMetrics] Found last day: ${lastDay}`);\n\n const newMetrics: CopilotMetrics[] = filterNewMetricsV2(\n copilotMetrics,\n lastDay,\n );\n logger.info(\n `[discoverEnterpriseMetrics] Found ${newMetrics.length} new metrics to insert`,\n );\n\n if (newMetrics.length > 0) {\n const coPilotMetrics = filterBaseMetrics(newMetrics, type);\n const ideCompletionsToInsert = filterIdeCompletionMetrics(\n newMetrics,\n type,\n );\n const ideCompletionsLanguagesToInsert =\n filterIdeCompletionLanguageMetrics(newMetrics, type);\n const ideCompletionsEditorsToInsert = filterIdeCompletionEditorMetrics(\n newMetrics,\n type,\n );\n const ideCompletionsEditorModelsToInsert =\n filterIdeCompletionEditorModelMetrics(newMetrics, type);\n const ideCompletionsEditorModelLanguagesToInsert =\n filterIdeCompletionEditorModelLanguageMetrics(newMetrics, type);\n const ideChats = filterIdeChatMetrics(newMetrics, type);\n const ideChatEditors = filterIdeEditorMetrics(newMetrics, type);\n const ideChatEditorModels = filterIdeChatEditorModelMetrics(\n newMetrics,\n type,\n );\n\n await batchInsertInChunks(coPilotMetrics, 30, async chunk => {\n await db.batchInsertMetrics(chunk);\n });\n\n await batchInsertInChunks(ideCompletionsToInsert, 30, async chunk => {\n await db.batchInsertIdeCompletions(chunk);\n });\n\n await batchInsertInChunks(\n ideCompletionsLanguagesToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsLanguages(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorsToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditors(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorModelsToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditorModels(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorModelLanguagesToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditorModelLanguages(chunk);\n },\n );\n\n await batchInsertInChunks(ideChats, 30, async chunk => {\n await db.batchInsertIdeChats(chunk);\n });\n\n await batchInsertInChunks(ideChatEditors, 30, async chunk => {\n await db.batchInsertIdeChatEditors(chunk);\n });\n\n await batchInsertInChunks(ideChatEditorModels, 30, async chunk => {\n await db.batchInsertIdeChatEditorModels(chunk);\n });\n\n const seats = await api.fetchEnterpriseSeats();\n const seatsToInsert = convertToSeatAnalysis(seats, type);\n await db.insertSeatAnalysys(seatsToInsert);\n\n logger.info(\n '[discoverEnterpriseMetrics] Inserted new metrics into the database',\n );\n } else {\n logger.info('[discoverEnterpriseMetrics] No new metrics found to insert');\n }\n } catch (error) {\n logger.error(\n '[discoverEnterpriseMetrics] An error occurred while processing Github Copilot metrics',\n error,\n );\n throw error;\n }\n}\n"],"names":["filterNewMetricsV2","filterBaseMetrics","filterIdeCompletionMetrics","filterIdeCompletionLanguageMetrics","filterIdeCompletionEditorMetrics","filterIdeCompletionEditorModelMetrics","filterIdeCompletionEditorModelLanguageMetrics","filterIdeChatMetrics","filterIdeEditorMetrics","filterIdeChatEditorModelMetrics","batchInsertInChunks","convertToSeatAnalysis"],"mappings":"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"}
1
+ {"version":3,"file":"EnterpriseTask.cjs.js","sources":["../../src/task/EnterpriseTask.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n CopilotMetrics,\n MetricsType,\n} from '@backstage-community/plugin-copilot-common';\nimport { batchInsertInChunks } from '../utils/batchInsert';\nimport { formatDate } from '../utils/dateUtils';\nimport {\n convertToSeatAnalysis,\n filterBaseMetrics,\n filterIdeChatEditorModelMetrics,\n filterIdeChatMetrics,\n filterIdeCompletionEditorMetrics,\n filterIdeCompletionEditorModelLanguageMetrics,\n filterIdeCompletionEditorModelMetrics,\n filterIdeCompletionLanguageMetrics,\n filterIdeCompletionMetrics,\n filterIdeEditorMetrics,\n filterNewMetricsV2,\n} from '../utils/metricHelpers';\nimport { TaskOptions } from './TaskManagement';\n\nexport async function discoverEnterpriseMetrics({\n api,\n logger,\n db,\n config,\n}: TaskOptions): Promise<void> {\n if (!config.getOptionalString('copilot.enterprise')) {\n logger.info(\n '[discoverEnterpriseMetrics] Skipping: Enterprise configuration not found.',\n );\n return;\n }\n\n const type: MetricsType = 'enterprise';\n\n try {\n const copilotMetrics = await api.fetchEnterpriseCopilotMetrics();\n logger.info(\n `[discoverEnterpriseMetrics] Fetched ${copilotMetrics.length} metrics`,\n );\n\n const lastDay = await db.getMostRecentDayFromMetricsV2(type);\n logger.info(\n `[discoverEnterpriseMetrics] Found last day: ${formatDate(lastDay)}`,\n );\n\n const newMetrics: CopilotMetrics[] = filterNewMetricsV2(\n copilotMetrics,\n lastDay,\n );\n logger.info(\n `[discoverEnterpriseMetrics] Found ${newMetrics.length} new metrics to insert`,\n );\n\n if (newMetrics.length > 0) {\n const coPilotMetrics = filterBaseMetrics(newMetrics, type);\n const ideCompletionsToInsert = filterIdeCompletionMetrics(\n newMetrics,\n type,\n );\n const ideCompletionsLanguagesToInsert =\n filterIdeCompletionLanguageMetrics(newMetrics, type);\n const ideCompletionsEditorsToInsert = filterIdeCompletionEditorMetrics(\n newMetrics,\n type,\n );\n const ideCompletionsEditorModelsToInsert =\n filterIdeCompletionEditorModelMetrics(newMetrics, type);\n const ideCompletionsEditorModelLanguagesToInsert =\n filterIdeCompletionEditorModelLanguageMetrics(newMetrics, type);\n const ideChats = filterIdeChatMetrics(newMetrics, type);\n const ideChatEditors = filterIdeEditorMetrics(newMetrics, type);\n const ideChatEditorModels = filterIdeChatEditorModelMetrics(\n newMetrics,\n type,\n );\n\n await batchInsertInChunks(coPilotMetrics, 30, async chunk => {\n await db.batchInsertMetrics(chunk);\n });\n\n await batchInsertInChunks(ideCompletionsToInsert, 30, async chunk => {\n await db.batchInsertIdeCompletions(chunk);\n });\n\n await batchInsertInChunks(\n ideCompletionsLanguagesToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsLanguages(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorsToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditors(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorModelsToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditorModels(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorModelLanguagesToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditorModelLanguages(chunk);\n },\n );\n\n await batchInsertInChunks(ideChats, 30, async chunk => {\n await db.batchInsertIdeChats(chunk);\n });\n\n await batchInsertInChunks(ideChatEditors, 30, async chunk => {\n await db.batchInsertIdeChatEditors(chunk);\n });\n\n await batchInsertInChunks(ideChatEditorModels, 30, async chunk => {\n await db.batchInsertIdeChatEditorModels(chunk);\n });\n\n const seats = await api.fetchEnterpriseSeats();\n const seatsToInsert = convertToSeatAnalysis(seats, type);\n await db.insertSeatAnalysys(seatsToInsert);\n\n logger.info(\n '[discoverEnterpriseMetrics] Inserted new metrics into the database',\n );\n } else {\n logger.info('[discoverEnterpriseMetrics] No new metrics found to insert');\n }\n } catch (error) {\n logger.error(\n `[discoverEnterpriseMetrics] Failed to process Github Copilot metrics.`,\n error,\n );\n throw error;\n }\n}\n"],"names":["formatDate","filterNewMetricsV2","filterBaseMetrics","filterIdeCompletionMetrics","filterIdeCompletionLanguageMetrics","filterIdeCompletionEditorMetrics","filterIdeCompletionEditorModelMetrics","filterIdeCompletionEditorModelLanguageMetrics","filterIdeChatMetrics","filterIdeEditorMetrics","filterIdeChatEditorModelMetrics","batchInsertInChunks","convertToSeatAnalysis"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var batchInsert = require('../utils/batchInsert.cjs.js');
4
+ var dateUtils = require('../utils/dateUtils.cjs.js');
4
5
  var metricHelpers = require('../utils/metricHelpers.cjs.js');
5
6
 
6
7
  async function discoverEnterpriseTeamMetrics({
@@ -35,7 +36,9 @@ async function discoverEnterpriseTeamMetrics({
35
36
  );
36
37
  const lastDay = await db.getMostRecentDayFromMetricsV2(type, team.slug);
37
38
  logger.info(
38
- `[discoverEnterpriseTeamMetrics] Found last day: ${lastDay}`
39
+ `[discoverEnterpriseTeamMetrics] Found last day: ${dateUtils.formatDate(
40
+ lastDay
41
+ )}`
39
42
  );
40
43
  const newMetrics = metricHelpers.filterNewMetricsV2(
41
44
  copilotMetrics,
@@ -129,13 +132,16 @@ async function discoverEnterpriseTeamMetrics({
129
132
  }
130
133
  } catch (error) {
131
134
  logger.error(
132
- `[discoverEnterpriseTeamMetrics] Error processing metrics for team ${team.slug}`,
135
+ `[discoverEnterpriseTeamMetrics] Failed to process metrics for team ${team.slug}.`,
133
136
  error
134
137
  );
135
138
  }
136
139
  }
137
140
  } catch (error) {
138
- logger.error("[discoverEnterpriseTeamMetrics] Error fetching teams", error);
141
+ logger.error(
142
+ `[discoverEnterpriseTeamMetrics] Failed to fetch teams.`,
143
+ error
144
+ );
139
145
  throw error;
140
146
  }
141
147
  }
@@ -1 +1 @@
1
- {"version":3,"file":"EnterpriseTeamTask.cjs.js","sources":["../../src/task/EnterpriseTeamTask.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n CopilotMetrics,\n MetricsType,\n} from '@backstage-community/plugin-copilot-common';\nimport { batchInsertInChunks } from '../utils/batchInsert';\nimport {\n convertToTeamSeatAnalysis,\n filterBaseMetrics,\n filterIdeChatEditorModelMetrics,\n filterIdeChatMetrics,\n filterIdeCompletionEditorMetrics,\n filterIdeCompletionEditorModelLanguageMetrics,\n filterIdeCompletionEditorModelMetrics,\n filterIdeCompletionLanguageMetrics,\n filterIdeCompletionMetrics,\n filterIdeEditorMetrics,\n filterNewMetricsV2,\n} from '../utils/metricHelpers';\nimport { TaskOptions } from './TaskManagement';\n\nexport async function discoverEnterpriseTeamMetrics({\n api,\n logger,\n db,\n config,\n}: TaskOptions): Promise<void> {\n if (!config.getOptionalString('copilot.enterprise')) {\n logger.info(\n '[discoverEnterpriseTeamMetrics] Skipping: Enterprise configuration not found.',\n );\n return;\n }\n\n const type: MetricsType = 'enterprise';\n\n try {\n const teams = await api.fetchEnterpriseTeams();\n logger.info(\n `[discoverEnterpriseTeamMetrics] Fetched ${teams.length} teams`,\n );\n\n // Fetch seat analysis\n const seats = await api.fetchEnterpriseSeats();\n logger.info(\n `[discoverEnterpriseTeamMetrics] Fetched ${seats.length} seats from enterprise`,\n );\n\n for (const team of teams) {\n try {\n logger.info(\n `[discoverEnterpriseTeamMetrics] Fetching metrics for team: ${team.slug}`,\n );\n\n const copilotMetrics = await api.fetchEnterpriseTeamCopilotMetrics(\n team.slug,\n );\n\n const lastDay = await db.getMostRecentDayFromMetricsV2(type, team.slug);\n logger.info(\n `[discoverEnterpriseTeamMetrics] Found last day: ${lastDay}`,\n );\n\n const newMetrics: CopilotMetrics[] = filterNewMetricsV2(\n copilotMetrics,\n lastDay,\n );\n logger.info(\n `[discoverEnterpriseTeamMetrics] Found ${newMetrics.length} new metrics to insert for team: ${team.slug}`,\n );\n\n if (newMetrics.length > 0) {\n const coPilotMetrics = filterBaseMetrics(newMetrics, type, team.slug);\n const ideCompletionsToInsert = filterIdeCompletionMetrics(\n newMetrics,\n type,\n team.slug,\n );\n const ideCompletionsLanguagesToInsert =\n filterIdeCompletionLanguageMetrics(newMetrics, type, team.slug);\n const ideCompletionsEditorsToInsert =\n filterIdeCompletionEditorMetrics(newMetrics, type, team.slug);\n const ideCompletionsEditorModelsToInsert =\n filterIdeCompletionEditorModelMetrics(newMetrics, type, team.slug);\n const ideCompletionsEditorModelLanguagesToInsert =\n filterIdeCompletionEditorModelLanguageMetrics(\n newMetrics,\n type,\n team.slug,\n );\n const ideChats = filterIdeChatMetrics(newMetrics, type, team.slug);\n const ideChatEditors = filterIdeEditorMetrics(\n newMetrics,\n type,\n team.slug,\n );\n const ideChatEditorModels = filterIdeChatEditorModelMetrics(\n newMetrics,\n type,\n team.slug,\n );\n\n await batchInsertInChunks(coPilotMetrics, 30, async chunk => {\n await db.batchInsertMetrics(chunk);\n });\n\n await batchInsertInChunks(ideCompletionsToInsert, 30, async chunk => {\n await db.batchInsertIdeCompletions(chunk);\n });\n\n await batchInsertInChunks(\n ideCompletionsLanguagesToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsLanguages(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorsToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditors(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorModelsToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditorModels(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorModelLanguagesToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditorModelLanguages(chunk);\n },\n );\n\n await batchInsertInChunks(ideChats, 30, async chunk => {\n await db.batchInsertIdeChats(chunk);\n });\n\n await batchInsertInChunks(ideChatEditors, 30, async chunk => {\n await db.batchInsertIdeChatEditors(chunk);\n });\n\n await batchInsertInChunks(ideChatEditorModels, 30, async chunk => {\n await db.batchInsertIdeChatEditorModels(chunk);\n });\n\n const seatsToInsert = convertToTeamSeatAnalysis(\n seats,\n type,\n team.slug,\n );\n await db.insertSeatAnalysys(seatsToInsert);\n\n logger.info(\n `[discoverEnterpriseTeamMetrics] Inserted new metrics into the database for team: ${team.slug}`,\n );\n } else {\n logger.info(\n `[discoverEnterpriseTeamMetrics] No new metrics found to insert for team: ${team.slug}`,\n );\n }\n } catch (error) {\n logger.error(\n `[discoverEnterpriseTeamMetrics] Error processing metrics for team ${team.slug}`,\n error,\n );\n }\n }\n } catch (error) {\n logger.error('[discoverEnterpriseTeamMetrics] Error fetching teams', error);\n throw error;\n }\n}\n"],"names":["filterNewMetricsV2","filterBaseMetrics","filterIdeCompletionMetrics","filterIdeCompletionLanguageMetrics","filterIdeCompletionEditorMetrics","filterIdeCompletionEditorModelMetrics","filterIdeCompletionEditorModelLanguageMetrics","filterIdeChatMetrics","filterIdeEditorMetrics","filterIdeChatEditorModelMetrics","batchInsertInChunks","convertToTeamSeatAnalysis"],"mappings":"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"}
1
+ {"version":3,"file":"EnterpriseTeamTask.cjs.js","sources":["../../src/task/EnterpriseTeamTask.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n CopilotMetrics,\n MetricsType,\n} from '@backstage-community/plugin-copilot-common';\nimport { batchInsertInChunks } from '../utils/batchInsert';\nimport { formatDate } from '../utils/dateUtils';\nimport {\n convertToTeamSeatAnalysis,\n filterBaseMetrics,\n filterIdeChatEditorModelMetrics,\n filterIdeChatMetrics,\n filterIdeCompletionEditorMetrics,\n filterIdeCompletionEditorModelLanguageMetrics,\n filterIdeCompletionEditorModelMetrics,\n filterIdeCompletionLanguageMetrics,\n filterIdeCompletionMetrics,\n filterIdeEditorMetrics,\n filterNewMetricsV2,\n} from '../utils/metricHelpers';\nimport { TaskOptions } from './TaskManagement';\n\nexport async function discoverEnterpriseTeamMetrics({\n api,\n logger,\n db,\n config,\n}: TaskOptions): Promise<void> {\n if (!config.getOptionalString('copilot.enterprise')) {\n logger.info(\n '[discoverEnterpriseTeamMetrics] Skipping: Enterprise configuration not found.',\n );\n return;\n }\n\n const type: MetricsType = 'enterprise';\n\n try {\n const teams = await api.fetchEnterpriseTeams();\n logger.info(\n `[discoverEnterpriseTeamMetrics] Fetched ${teams.length} teams`,\n );\n\n // Fetch seat analysis\n const seats = await api.fetchEnterpriseSeats();\n logger.info(\n `[discoverEnterpriseTeamMetrics] Fetched ${seats.length} seats from enterprise`,\n );\n\n for (const team of teams) {\n try {\n logger.info(\n `[discoverEnterpriseTeamMetrics] Fetching metrics for team: ${team.slug}`,\n );\n\n const copilotMetrics = await api.fetchEnterpriseTeamCopilotMetrics(\n team.slug,\n );\n\n const lastDay = await db.getMostRecentDayFromMetricsV2(type, team.slug);\n logger.info(\n `[discoverEnterpriseTeamMetrics] Found last day: ${formatDate(\n lastDay,\n )}`,\n );\n\n const newMetrics: CopilotMetrics[] = filterNewMetricsV2(\n copilotMetrics,\n lastDay,\n );\n logger.info(\n `[discoverEnterpriseTeamMetrics] Found ${newMetrics.length} new metrics to insert for team: ${team.slug}`,\n );\n\n if (newMetrics.length > 0) {\n const coPilotMetrics = filterBaseMetrics(newMetrics, type, team.slug);\n const ideCompletionsToInsert = filterIdeCompletionMetrics(\n newMetrics,\n type,\n team.slug,\n );\n const ideCompletionsLanguagesToInsert =\n filterIdeCompletionLanguageMetrics(newMetrics, type, team.slug);\n const ideCompletionsEditorsToInsert =\n filterIdeCompletionEditorMetrics(newMetrics, type, team.slug);\n const ideCompletionsEditorModelsToInsert =\n filterIdeCompletionEditorModelMetrics(newMetrics, type, team.slug);\n const ideCompletionsEditorModelLanguagesToInsert =\n filterIdeCompletionEditorModelLanguageMetrics(\n newMetrics,\n type,\n team.slug,\n );\n const ideChats = filterIdeChatMetrics(newMetrics, type, team.slug);\n const ideChatEditors = filterIdeEditorMetrics(\n newMetrics,\n type,\n team.slug,\n );\n const ideChatEditorModels = filterIdeChatEditorModelMetrics(\n newMetrics,\n type,\n team.slug,\n );\n\n await batchInsertInChunks(coPilotMetrics, 30, async chunk => {\n await db.batchInsertMetrics(chunk);\n });\n\n await batchInsertInChunks(ideCompletionsToInsert, 30, async chunk => {\n await db.batchInsertIdeCompletions(chunk);\n });\n\n await batchInsertInChunks(\n ideCompletionsLanguagesToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsLanguages(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorsToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditors(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorModelsToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditorModels(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorModelLanguagesToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditorModelLanguages(chunk);\n },\n );\n\n await batchInsertInChunks(ideChats, 30, async chunk => {\n await db.batchInsertIdeChats(chunk);\n });\n\n await batchInsertInChunks(ideChatEditors, 30, async chunk => {\n await db.batchInsertIdeChatEditors(chunk);\n });\n\n await batchInsertInChunks(ideChatEditorModels, 30, async chunk => {\n await db.batchInsertIdeChatEditorModels(chunk);\n });\n\n const seatsToInsert = convertToTeamSeatAnalysis(\n seats,\n type,\n team.slug,\n );\n await db.insertSeatAnalysys(seatsToInsert);\n\n logger.info(\n `[discoverEnterpriseTeamMetrics] Inserted new metrics into the database for team: ${team.slug}`,\n );\n } else {\n logger.info(\n `[discoverEnterpriseTeamMetrics] No new metrics found to insert for team: ${team.slug}`,\n );\n }\n } catch (error) {\n logger.error(\n `[discoverEnterpriseTeamMetrics] Failed to process metrics for team ${team.slug}.`,\n error,\n );\n }\n }\n } catch (error) {\n logger.error(\n `[discoverEnterpriseTeamMetrics] Failed to fetch teams.`,\n error,\n );\n throw error;\n }\n}\n"],"names":["formatDate","filterNewMetricsV2","filterBaseMetrics","filterIdeCompletionMetrics","filterIdeCompletionLanguageMetrics","filterIdeCompletionEditorMetrics","filterIdeCompletionEditorModelMetrics","filterIdeCompletionEditorModelLanguageMetrics","filterIdeChatMetrics","filterIdeEditorMetrics","filterIdeChatEditorModelMetrics","batchInsertInChunks","convertToTeamSeatAnalysis"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var batchInsert = require('../utils/batchInsert.cjs.js');
4
+ var dateUtils = require('../utils/dateUtils.cjs.js');
4
5
  var metricHelpers = require('../utils/metricHelpers.cjs.js');
5
6
 
6
7
  async function discoverOrganizationMetrics({
@@ -22,7 +23,9 @@ async function discoverOrganizationMetrics({
22
23
  `[discoverOrganizationMetrics] Fetched ${copilotMetrics.length} metrics`
23
24
  );
24
25
  const lastDay = await db.getMostRecentDayFromMetricsV2(type);
25
- logger.info(`[discoverOrganizationMetrics] Found last day: ${lastDay}`);
26
+ logger.info(
27
+ `[discoverOrganizationMetrics] Found last day: ${dateUtils.formatDate(lastDay)}`
28
+ );
26
29
  const newMetrics = metricHelpers.filterNewMetricsV2(
27
30
  copilotMetrics,
28
31
  lastDay
@@ -105,7 +108,7 @@ async function discoverOrganizationMetrics({
105
108
  }
106
109
  } catch (error) {
107
110
  logger.error(
108
- "[discoverOrganizationMetrics] An error occurred while processing Github Copilot metrics",
111
+ `[discoverOrganizationMetrics] Failed to process Github Copilot metrics.`,
109
112
  error
110
113
  );
111
114
  throw error;
@@ -1 +1 @@
1
- {"version":3,"file":"OrganizationTask.cjs.js","sources":["../../src/task/OrganizationTask.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n MetricsType,\n CopilotMetrics,\n} from '@backstage-community/plugin-copilot-common';\nimport { batchInsertInChunks } from '../utils/batchInsert';\nimport {\n filterBaseMetrics,\n filterNewMetricsV2,\n filterIdeCompletionMetrics,\n filterIdeCompletionLanguageMetrics,\n filterIdeCompletionEditorMetrics,\n filterIdeCompletionEditorModelMetrics,\n filterIdeCompletionEditorModelLanguageMetrics,\n filterIdeChatMetrics,\n filterIdeEditorMetrics,\n filterIdeChatEditorModelMetrics,\n convertToSeatAnalysis,\n} from '../utils/metricHelpers';\nimport { TaskOptions } from './TaskManagement';\n\nexport async function discoverOrganizationMetrics({\n api,\n logger,\n db,\n config,\n}: TaskOptions): Promise<void> {\n if (!config.getOptionalString('copilot.organization')) {\n logger.info(\n '[discoverOrganizationMetrics] Skipping: Organization configuration not found.',\n );\n return;\n }\n\n const type: MetricsType = 'organization';\n\n try {\n const copilotMetrics = await api.fetchOrganizationCopilotMetrics();\n logger.info(\n `[discoverOrganizationMetrics] Fetched ${copilotMetrics.length} metrics`,\n );\n\n const lastDay = await db.getMostRecentDayFromMetricsV2(type);\n logger.info(`[discoverOrganizationMetrics] Found last day: ${lastDay}`);\n\n const newMetrics: CopilotMetrics[] = filterNewMetricsV2(\n copilotMetrics,\n lastDay,\n );\n logger.info(\n `[discoverOrganizationMetrics] Found ${newMetrics.length} new metrics to insert`,\n );\n\n if (newMetrics.length > 0) {\n const coPilotMetrics = filterBaseMetrics(newMetrics, type);\n const ideCompletionsToInsert = filterIdeCompletionMetrics(\n newMetrics,\n type,\n );\n const ideCompletionsLanguagesToInsert =\n filterIdeCompletionLanguageMetrics(newMetrics, type);\n const ideCompletionsEditorsToInsert = filterIdeCompletionEditorMetrics(\n newMetrics,\n type,\n );\n const ideCompletionsEditorModelsToInsert =\n filterIdeCompletionEditorModelMetrics(newMetrics, type);\n const ideCompletionsEditorModelLanguagesToInsert =\n filterIdeCompletionEditorModelLanguageMetrics(newMetrics, type);\n const ideChats = filterIdeChatMetrics(newMetrics, type);\n const ideChatEditors = filterIdeEditorMetrics(newMetrics, type);\n const ideChatEditorModels = filterIdeChatEditorModelMetrics(\n newMetrics,\n type,\n );\n\n await batchInsertInChunks(coPilotMetrics, 30, async chunk => {\n await db.batchInsertMetrics(chunk);\n });\n\n await batchInsertInChunks(ideCompletionsToInsert, 30, async chunk => {\n await db.batchInsertIdeCompletions(chunk);\n });\n\n await batchInsertInChunks(\n ideCompletionsLanguagesToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsLanguages(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorsToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditors(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorModelsToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditorModels(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorModelLanguagesToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditorModelLanguages(chunk);\n },\n );\n\n await batchInsertInChunks(ideChats, 30, async chunk => {\n await db.batchInsertIdeChats(chunk);\n });\n\n await batchInsertInChunks(ideChatEditors, 30, async chunk => {\n await db.batchInsertIdeChatEditors(chunk);\n });\n\n await batchInsertInChunks(ideChatEditorModels, 30, async chunk => {\n await db.batchInsertIdeChatEditorModels(chunk);\n });\n\n const seats = await api.fetchOrganizationSeats();\n const seatsToInsert = convertToSeatAnalysis(seats, type);\n await db.insertSeatAnalysys(seatsToInsert);\n\n logger.info(\n '[discoverOrganizationMetrics] Inserted new metrics into the database',\n );\n } else {\n logger.info(\n '[discoverOrganizationMetrics] No new metrics found to insert',\n );\n }\n } catch (error) {\n logger.error(\n '[discoverOrganizationMetrics] An error occurred while processing Github Copilot metrics',\n error,\n );\n throw error;\n }\n}\n"],"names":["filterNewMetricsV2","filterBaseMetrics","filterIdeCompletionMetrics","filterIdeCompletionLanguageMetrics","filterIdeCompletionEditorMetrics","filterIdeCompletionEditorModelMetrics","filterIdeCompletionEditorModelLanguageMetrics","filterIdeChatMetrics","filterIdeEditorMetrics","filterIdeChatEditorModelMetrics","batchInsertInChunks","convertToSeatAnalysis"],"mappings":"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"}
1
+ {"version":3,"file":"OrganizationTask.cjs.js","sources":["../../src/task/OrganizationTask.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n MetricsType,\n CopilotMetrics,\n} from '@backstage-community/plugin-copilot-common';\nimport { batchInsertInChunks } from '../utils/batchInsert';\nimport { formatDate } from '../utils/dateUtils';\nimport {\n filterBaseMetrics,\n filterNewMetricsV2,\n filterIdeCompletionMetrics,\n filterIdeCompletionLanguageMetrics,\n filterIdeCompletionEditorMetrics,\n filterIdeCompletionEditorModelMetrics,\n filterIdeCompletionEditorModelLanguageMetrics,\n filterIdeChatMetrics,\n filterIdeEditorMetrics,\n filterIdeChatEditorModelMetrics,\n convertToSeatAnalysis,\n} from '../utils/metricHelpers';\nimport { TaskOptions } from './TaskManagement';\n\nexport async function discoverOrganizationMetrics({\n api,\n logger,\n db,\n config,\n}: TaskOptions): Promise<void> {\n if (!config.getOptionalString('copilot.organization')) {\n logger.info(\n '[discoverOrganizationMetrics] Skipping: Organization configuration not found.',\n );\n return;\n }\n\n const type: MetricsType = 'organization';\n\n try {\n const copilotMetrics = await api.fetchOrganizationCopilotMetrics();\n logger.info(\n `[discoverOrganizationMetrics] Fetched ${copilotMetrics.length} metrics`,\n );\n\n const lastDay = await db.getMostRecentDayFromMetricsV2(type);\n logger.info(\n `[discoverOrganizationMetrics] Found last day: ${formatDate(lastDay)}`,\n );\n\n const newMetrics: CopilotMetrics[] = filterNewMetricsV2(\n copilotMetrics,\n lastDay,\n );\n logger.info(\n `[discoverOrganizationMetrics] Found ${newMetrics.length} new metrics to insert`,\n );\n\n if (newMetrics.length > 0) {\n const coPilotMetrics = filterBaseMetrics(newMetrics, type);\n const ideCompletionsToInsert = filterIdeCompletionMetrics(\n newMetrics,\n type,\n );\n const ideCompletionsLanguagesToInsert =\n filterIdeCompletionLanguageMetrics(newMetrics, type);\n const ideCompletionsEditorsToInsert = filterIdeCompletionEditorMetrics(\n newMetrics,\n type,\n );\n const ideCompletionsEditorModelsToInsert =\n filterIdeCompletionEditorModelMetrics(newMetrics, type);\n const ideCompletionsEditorModelLanguagesToInsert =\n filterIdeCompletionEditorModelLanguageMetrics(newMetrics, type);\n const ideChats = filterIdeChatMetrics(newMetrics, type);\n const ideChatEditors = filterIdeEditorMetrics(newMetrics, type);\n const ideChatEditorModels = filterIdeChatEditorModelMetrics(\n newMetrics,\n type,\n );\n\n await batchInsertInChunks(coPilotMetrics, 30, async chunk => {\n await db.batchInsertMetrics(chunk);\n });\n\n await batchInsertInChunks(ideCompletionsToInsert, 30, async chunk => {\n await db.batchInsertIdeCompletions(chunk);\n });\n\n await batchInsertInChunks(\n ideCompletionsLanguagesToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsLanguages(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorsToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditors(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorModelsToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditorModels(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorModelLanguagesToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditorModelLanguages(chunk);\n },\n );\n\n await batchInsertInChunks(ideChats, 30, async chunk => {\n await db.batchInsertIdeChats(chunk);\n });\n\n await batchInsertInChunks(ideChatEditors, 30, async chunk => {\n await db.batchInsertIdeChatEditors(chunk);\n });\n\n await batchInsertInChunks(ideChatEditorModels, 30, async chunk => {\n await db.batchInsertIdeChatEditorModels(chunk);\n });\n\n const seats = await api.fetchOrganizationSeats();\n const seatsToInsert = convertToSeatAnalysis(seats, type);\n await db.insertSeatAnalysys(seatsToInsert);\n\n logger.info(\n '[discoverOrganizationMetrics] Inserted new metrics into the database',\n );\n } else {\n logger.info(\n '[discoverOrganizationMetrics] No new metrics found to insert',\n );\n }\n } catch (error) {\n logger.error(\n `[discoverOrganizationMetrics] Failed to process Github Copilot metrics.`,\n error,\n );\n throw error;\n }\n}\n"],"names":["formatDate","filterNewMetricsV2","filterBaseMetrics","filterIdeCompletionMetrics","filterIdeCompletionLanguageMetrics","filterIdeCompletionEditorMetrics","filterIdeCompletionEditorModelMetrics","filterIdeCompletionEditorModelLanguageMetrics","filterIdeChatMetrics","filterIdeEditorMetrics","filterIdeChatEditorModelMetrics","batchInsertInChunks","convertToSeatAnalysis"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var batchInsert = require('../utils/batchInsert.cjs.js');
4
+ var dateUtils = require('../utils/dateUtils.cjs.js');
4
5
  var metricHelpers = require('../utils/metricHelpers.cjs.js');
5
6
 
6
7
  async function discoverOrganizationTeamMetrics({
@@ -35,7 +36,9 @@ async function discoverOrganizationTeamMetrics({
35
36
  );
36
37
  const lastDay = await db.getMostRecentDayFromMetricsV2(type, team.slug);
37
38
  logger.info(
38
- `[discoverOrganizationTeamMetrics] Found last day: ${lastDay}`
39
+ `[discoverOrganizationTeamMetrics] Found last day: ${dateUtils.formatDate(
40
+ lastDay
41
+ )}`
39
42
  );
40
43
  const newMetrics = metricHelpers.filterNewMetricsV2(
41
44
  copilotMetrics,
@@ -129,14 +132,14 @@ async function discoverOrganizationTeamMetrics({
129
132
  }
130
133
  } catch (error) {
131
134
  logger.error(
132
- `[discoverOrganizationTeamMetrics] Error processing metrics for team ${team.slug}`,
135
+ `[discoverOrganizationTeamMetrics] Failed to process metrics for team ${team.slug}.`,
133
136
  error
134
137
  );
135
138
  }
136
139
  }
137
140
  } catch (error) {
138
141
  logger.error(
139
- "[discoverOrganizationTeamMetrics] Error fetching teams",
142
+ `[discoverOrganizationTeamMetrics] Failed to fetch teams.`,
140
143
  error
141
144
  );
142
145
  throw error;
@@ -1 +1 @@
1
- {"version":3,"file":"OrganizationTeamTask.cjs.js","sources":["../../src/task/OrganizationTeamTask.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n CopilotMetrics,\n MetricsType,\n} from '@backstage-community/plugin-copilot-common';\nimport { batchInsertInChunks } from '../utils/batchInsert';\nimport {\n convertToTeamSeatAnalysis,\n filterBaseMetrics,\n filterIdeChatEditorModelMetrics,\n filterIdeChatMetrics,\n filterIdeCompletionEditorMetrics,\n filterIdeCompletionEditorModelLanguageMetrics,\n filterIdeCompletionEditorModelMetrics,\n filterIdeCompletionLanguageMetrics,\n filterIdeCompletionMetrics,\n filterIdeEditorMetrics,\n filterNewMetricsV2,\n} from '../utils/metricHelpers';\nimport { TaskOptions } from './TaskManagement';\n\nexport async function discoverOrganizationTeamMetrics({\n api,\n logger,\n db,\n config,\n}: TaskOptions): Promise<void> {\n if (!config.getOptionalString('copilot.organization')) {\n logger.info(\n '[discoverOrganizationTeamMetrics] Skipping: Organization configuration not found.',\n );\n return;\n }\n\n const type: MetricsType = 'organization';\n\n try {\n const teams = await api.fetchOrganizationTeams();\n logger.info(\n `[discoverOrganizationTeamMetrics] Fetched ${teams.length} teams`,\n );\n\n // Fetch seat analysis\n const seats = await api.fetchOrganizationSeats();\n logger.info(\n `[discoverOrganizationTeamMetrics] Fetched ${seats.seats.length} seats from organization`,\n );\n\n for (const team of teams) {\n try {\n logger.info(\n `[discoverOrganizationTeamMetrics] Fetching metrics for team: ${team.slug}`,\n );\n\n const copilotMetrics = await api.fetchOrganizationTeamCopilotMetrics(\n team.slug,\n );\n\n const lastDay = await db.getMostRecentDayFromMetricsV2(type, team.slug);\n logger.info(\n `[discoverOrganizationTeamMetrics] Found last day: ${lastDay}`,\n );\n\n const newMetrics: CopilotMetrics[] = filterNewMetricsV2(\n copilotMetrics,\n lastDay,\n );\n logger.info(\n `[discoverOrganizationTeamMetrics] Found ${newMetrics.length} new metrics to insert for team: ${team.slug}`,\n );\n\n if (newMetrics.length > 0) {\n const coPilotMetrics = filterBaseMetrics(newMetrics, type, team.slug);\n const ideCompletionsToInsert = filterIdeCompletionMetrics(\n newMetrics,\n type,\n team.slug,\n );\n const ideCompletionsLanguagesToInsert =\n filterIdeCompletionLanguageMetrics(newMetrics, type, team.slug);\n const ideCompletionsEditorsToInsert =\n filterIdeCompletionEditorMetrics(newMetrics, type, team.slug);\n const ideCompletionsEditorModelsToInsert =\n filterIdeCompletionEditorModelMetrics(newMetrics, type, team.slug);\n const ideCompletionsEditorModelLanguagesToInsert =\n filterIdeCompletionEditorModelLanguageMetrics(\n newMetrics,\n type,\n team.slug,\n );\n const ideChats = filterIdeChatMetrics(newMetrics, type, team.slug);\n const ideChatEditors = filterIdeEditorMetrics(\n newMetrics,\n type,\n team.slug,\n );\n const ideChatEditorModels = filterIdeChatEditorModelMetrics(\n newMetrics,\n type,\n team.slug,\n );\n\n await batchInsertInChunks(coPilotMetrics, 30, async chunk => {\n await db.batchInsertMetrics(chunk);\n });\n\n await batchInsertInChunks(ideCompletionsToInsert, 30, async chunk => {\n await db.batchInsertIdeCompletions(chunk);\n });\n\n await batchInsertInChunks(\n ideCompletionsLanguagesToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsLanguages(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorsToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditors(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorModelsToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditorModels(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorModelLanguagesToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditorModelLanguages(chunk);\n },\n );\n\n await batchInsertInChunks(ideChats, 30, async chunk => {\n await db.batchInsertIdeChats(chunk);\n });\n\n await batchInsertInChunks(ideChatEditors, 30, async chunk => {\n await db.batchInsertIdeChatEditors(chunk);\n });\n\n await batchInsertInChunks(ideChatEditorModels, 30, async chunk => {\n await db.batchInsertIdeChatEditorModels(chunk);\n });\n\n const seatsToInsert = convertToTeamSeatAnalysis(\n seats,\n type,\n team.slug,\n );\n await db.insertSeatAnalysys(seatsToInsert);\n\n logger.info(\n `[discoverOrganizationTeamMetrics] Inserted new metrics into the database for team: ${team.slug}`,\n );\n } else {\n logger.info(\n `[discoverOrganizationTeamMetrics] No new metrics found to insert for team: ${team.slug}`,\n );\n }\n } catch (error) {\n logger.error(\n `[discoverOrganizationTeamMetrics] Error processing metrics for team ${team.slug}`,\n error,\n );\n }\n }\n } catch (error) {\n logger.error(\n '[discoverOrganizationTeamMetrics] Error fetching teams',\n error,\n );\n throw error;\n }\n}\n"],"names":["filterNewMetricsV2","filterBaseMetrics","filterIdeCompletionMetrics","filterIdeCompletionLanguageMetrics","filterIdeCompletionEditorMetrics","filterIdeCompletionEditorModelMetrics","filterIdeCompletionEditorModelLanguageMetrics","filterIdeChatMetrics","filterIdeEditorMetrics","filterIdeChatEditorModelMetrics","batchInsertInChunks","convertToTeamSeatAnalysis"],"mappings":"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"}
1
+ {"version":3,"file":"OrganizationTeamTask.cjs.js","sources":["../../src/task/OrganizationTeamTask.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n CopilotMetrics,\n MetricsType,\n} from '@backstage-community/plugin-copilot-common';\nimport { batchInsertInChunks } from '../utils/batchInsert';\nimport { formatDate } from '../utils/dateUtils';\nimport {\n convertToTeamSeatAnalysis,\n filterBaseMetrics,\n filterIdeChatEditorModelMetrics,\n filterIdeChatMetrics,\n filterIdeCompletionEditorMetrics,\n filterIdeCompletionEditorModelLanguageMetrics,\n filterIdeCompletionEditorModelMetrics,\n filterIdeCompletionLanguageMetrics,\n filterIdeCompletionMetrics,\n filterIdeEditorMetrics,\n filterNewMetricsV2,\n} from '../utils/metricHelpers';\nimport { TaskOptions } from './TaskManagement';\n\nexport async function discoverOrganizationTeamMetrics({\n api,\n logger,\n db,\n config,\n}: TaskOptions): Promise<void> {\n if (!config.getOptionalString('copilot.organization')) {\n logger.info(\n '[discoverOrganizationTeamMetrics] Skipping: Organization configuration not found.',\n );\n return;\n }\n\n const type: MetricsType = 'organization';\n\n try {\n const teams = await api.fetchOrganizationTeams();\n logger.info(\n `[discoverOrganizationTeamMetrics] Fetched ${teams.length} teams`,\n );\n\n // Fetch seat analysis\n const seats = await api.fetchOrganizationSeats();\n logger.info(\n `[discoverOrganizationTeamMetrics] Fetched ${seats.seats.length} seats from organization`,\n );\n\n for (const team of teams) {\n try {\n logger.info(\n `[discoverOrganizationTeamMetrics] Fetching metrics for team: ${team.slug}`,\n );\n\n const copilotMetrics = await api.fetchOrganizationTeamCopilotMetrics(\n team.slug,\n );\n\n const lastDay = await db.getMostRecentDayFromMetricsV2(type, team.slug);\n logger.info(\n `[discoverOrganizationTeamMetrics] Found last day: ${formatDate(\n lastDay,\n )}`,\n );\n\n const newMetrics: CopilotMetrics[] = filterNewMetricsV2(\n copilotMetrics,\n lastDay,\n );\n logger.info(\n `[discoverOrganizationTeamMetrics] Found ${newMetrics.length} new metrics to insert for team: ${team.slug}`,\n );\n\n if (newMetrics.length > 0) {\n const coPilotMetrics = filterBaseMetrics(newMetrics, type, team.slug);\n const ideCompletionsToInsert = filterIdeCompletionMetrics(\n newMetrics,\n type,\n team.slug,\n );\n const ideCompletionsLanguagesToInsert =\n filterIdeCompletionLanguageMetrics(newMetrics, type, team.slug);\n const ideCompletionsEditorsToInsert =\n filterIdeCompletionEditorMetrics(newMetrics, type, team.slug);\n const ideCompletionsEditorModelsToInsert =\n filterIdeCompletionEditorModelMetrics(newMetrics, type, team.slug);\n const ideCompletionsEditorModelLanguagesToInsert =\n filterIdeCompletionEditorModelLanguageMetrics(\n newMetrics,\n type,\n team.slug,\n );\n const ideChats = filterIdeChatMetrics(newMetrics, type, team.slug);\n const ideChatEditors = filterIdeEditorMetrics(\n newMetrics,\n type,\n team.slug,\n );\n const ideChatEditorModels = filterIdeChatEditorModelMetrics(\n newMetrics,\n type,\n team.slug,\n );\n\n await batchInsertInChunks(coPilotMetrics, 30, async chunk => {\n await db.batchInsertMetrics(chunk);\n });\n\n await batchInsertInChunks(ideCompletionsToInsert, 30, async chunk => {\n await db.batchInsertIdeCompletions(chunk);\n });\n\n await batchInsertInChunks(\n ideCompletionsLanguagesToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsLanguages(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorsToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditors(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorModelsToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditorModels(chunk);\n },\n );\n\n await batchInsertInChunks(\n ideCompletionsEditorModelLanguagesToInsert,\n 30,\n async chunk => {\n await db.batchInsertIdeCompletionsEditorModelLanguages(chunk);\n },\n );\n\n await batchInsertInChunks(ideChats, 30, async chunk => {\n await db.batchInsertIdeChats(chunk);\n });\n\n await batchInsertInChunks(ideChatEditors, 30, async chunk => {\n await db.batchInsertIdeChatEditors(chunk);\n });\n\n await batchInsertInChunks(ideChatEditorModels, 30, async chunk => {\n await db.batchInsertIdeChatEditorModels(chunk);\n });\n\n const seatsToInsert = convertToTeamSeatAnalysis(\n seats,\n type,\n team.slug,\n );\n await db.insertSeatAnalysys(seatsToInsert);\n\n logger.info(\n `[discoverOrganizationTeamMetrics] Inserted new metrics into the database for team: ${team.slug}`,\n );\n } else {\n logger.info(\n `[discoverOrganizationTeamMetrics] No new metrics found to insert for team: ${team.slug}`,\n );\n }\n } catch (error) {\n logger.error(\n `[discoverOrganizationTeamMetrics] Failed to process metrics for team ${team.slug}.`,\n error,\n );\n }\n }\n } catch (error) {\n logger.error(\n `[discoverOrganizationTeamMetrics] Failed to fetch teams.`,\n error,\n );\n throw error;\n }\n}\n"],"names":["formatDate","filterNewMetricsV2","filterBaseMetrics","filterIdeCompletionMetrics","filterIdeCompletionLanguageMetrics","filterIdeCompletionEditorMetrics","filterIdeCompletionEditorModelMetrics","filterIdeCompletionEditorModelLanguageMetrics","filterIdeChatMetrics","filterIdeEditorMetrics","filterIdeChatEditorModelMetrics","batchInsertInChunks","convertToTeamSeatAnalysis"],"mappings":"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"}
@@ -25,21 +25,15 @@ class TaskManagement {
25
25
  this.options.logger.info(
26
26
  `[TaskManagement] Starting processing of ${this.tasks.length} tasks`
27
27
  );
28
- const taskPromises = this.tasks.map(async (task) => {
28
+ const taskPromises = this.tasks.map(async (task, index) => {
29
29
  try {
30
30
  await task();
31
- } catch (e) {
32
- if (e instanceof Error) {
33
- this.options.logger.error(
34
- `[TaskManagement] Failed to process task: ${e.message}`,
35
- e
36
- );
37
- } else {
38
- this.options.logger.error(
39
- "[TaskManagement] Failed to process task",
40
- e
41
- );
42
- }
31
+ } catch (error) {
32
+ const taskName = task.name || `Task ${index + 1}`;
33
+ this.options.logger.error(
34
+ `[TaskManagement] Failed to process ${taskName}.`,
35
+ error
36
+ );
43
37
  }
44
38
  });
45
39
  await Promise.all(taskPromises);
@@ -1 +1 @@
1
- {"version":3,"file":"TaskManagement.cjs.js","sources":["../../src/task/TaskManagement.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Config } from '@backstage/config';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { GithubClient } from '../client/GithubClient';\nimport { DatabaseHandler } from '../db/DatabaseHandler';\nimport { discoverOrganizationMetrics } from './OrganizationTask';\nimport { discoverOrganizationTeamMetrics } from './OrganizationTeamTask';\nimport { discoverEnterpriseMetrics } from './EnterpriseTask';\nimport { discoverEnterpriseTeamMetrics } from './EnterpriseTeamTask';\n\nexport type TaskOptions = {\n api: GithubClient;\n config: Config;\n logger: LoggerService;\n db: DatabaseHandler;\n};\n\nexport default class TaskManagement {\n private readonly tasks: Array<() => Promise<void>>;\n\n constructor(private readonly options: TaskOptions) {\n this.tasks = [\n () => discoverOrganizationMetrics(this.options),\n () => discoverOrganizationTeamMetrics(this.options),\n () => discoverEnterpriseMetrics(this.options),\n () => discoverEnterpriseTeamMetrics(this.options),\n ];\n }\n\n static create(options: TaskOptions) {\n return new TaskManagement(options);\n }\n\n async runAsync() {\n this.options.logger.info(\n `[TaskManagement] Starting processing of ${this.tasks.length} tasks`,\n );\n\n const taskPromises = this.tasks.map(async task => {\n try {\n await task();\n } catch (e) {\n if (e instanceof Error) {\n this.options.logger.error(\n `[TaskManagement] Failed to process task: ${e.message}`,\n e,\n );\n } else {\n this.options.logger.error(\n '[TaskManagement] Failed to process task',\n e,\n );\n }\n }\n });\n\n await Promise.all(taskPromises);\n\n this.options.logger.info(\n `[TaskManagement] Completed processing of all tasks`,\n );\n }\n}\n"],"names":["discoverOrganizationMetrics","discoverOrganizationTeamMetrics","discoverEnterpriseMetrics","discoverEnterpriseTeamMetrics"],"mappings":";;;;;;;;;AA+BA,MAAqB,cAAe,CAAA;AAAA,EAGlC,YAA6B,OAAsB,EAAA;AAAtB,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAC3B,IAAA,IAAA,CAAK,KAAQ,GAAA;AAAA,MACX,MAAMA,4CAA4B,CAAA,IAAA,CAAK,OAAO,CAAA;AAAA,MAC9C,MAAMC,oDAAgC,CAAA,IAAA,CAAK,OAAO,CAAA;AAAA,MAClD,MAAMC,wCAA0B,CAAA,IAAA,CAAK,OAAO,CAAA;AAAA,MAC5C,MAAMC,gDAA8B,CAAA,IAAA,CAAK,OAAO;AAAA,KAClD;AAAA;AACF,EATiB,KAAA;AAAA,EAWjB,OAAO,OAAO,OAAsB,EAAA;AAClC,IAAO,OAAA,IAAI,eAAe,OAAO,CAAA;AAAA;AACnC,EAEA,MAAM,QAAW,GAAA;AACf,IAAA,IAAA,CAAK,QAAQ,MAAO,CAAA,IAAA;AAAA,MAClB,CAAA,wCAAA,EAA2C,IAAK,CAAA,KAAA,CAAM,MAAM,CAAA,MAAA;AAAA,KAC9D;AAEA,IAAA,MAAM,YAAe,GAAA,IAAA,CAAK,KAAM,CAAA,GAAA,CAAI,OAAM,IAAQ,KAAA;AAChD,MAAI,IAAA;AACF,QAAA,MAAM,IAAK,EAAA;AAAA,eACJ,CAAG,EAAA;AACV,QAAA,IAAI,aAAa,KAAO,EAAA;AACtB,UAAA,IAAA,CAAK,QAAQ,MAAO,CAAA,KAAA;AAAA,YAClB,CAAA,yCAAA,EAA4C,EAAE,OAAO,CAAA,CAAA;AAAA,YACrD;AAAA,WACF;AAAA,SACK,MAAA;AACL,UAAA,IAAA,CAAK,QAAQ,MAAO,CAAA,KAAA;AAAA,YAClB,yCAAA;AAAA,YACA;AAAA,WACF;AAAA;AACF;AACF,KACD,CAAA;AAED,IAAM,MAAA,OAAA,CAAQ,IAAI,YAAY,CAAA;AAE9B,IAAA,IAAA,CAAK,QAAQ,MAAO,CAAA,IAAA;AAAA,MAClB,CAAA,kDAAA;AAAA,KACF;AAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"TaskManagement.cjs.js","sources":["../../src/task/TaskManagement.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Config } from '@backstage/config';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { GithubClient } from '../client/GithubClient';\nimport { DatabaseHandler } from '../db/DatabaseHandler';\nimport { discoverOrganizationMetrics } from './OrganizationTask';\nimport { discoverOrganizationTeamMetrics } from './OrganizationTeamTask';\nimport { discoverEnterpriseMetrics } from './EnterpriseTask';\nimport { discoverEnterpriseTeamMetrics } from './EnterpriseTeamTask';\n\nexport type TaskOptions = {\n api: GithubClient;\n config: Config;\n logger: LoggerService;\n db: DatabaseHandler;\n};\n\nexport default class TaskManagement {\n private readonly tasks: Array<() => Promise<void>>;\n\n constructor(private readonly options: TaskOptions) {\n this.tasks = [\n () => discoverOrganizationMetrics(this.options),\n () => discoverOrganizationTeamMetrics(this.options),\n () => discoverEnterpriseMetrics(this.options),\n () => discoverEnterpriseTeamMetrics(this.options),\n ];\n }\n\n static create(options: TaskOptions) {\n return new TaskManagement(options);\n }\n\n async runAsync() {\n this.options.logger.info(\n `[TaskManagement] Starting processing of ${this.tasks.length} tasks`,\n );\n\n const taskPromises = this.tasks.map(async (task, index) => {\n try {\n await task();\n } catch (error) {\n const taskName = task.name || `Task ${index + 1}`;\n this.options.logger.error(\n `[TaskManagement] Failed to process ${taskName}.`,\n error,\n );\n }\n });\n\n await Promise.all(taskPromises);\n\n this.options.logger.info(\n `[TaskManagement] Completed processing of all tasks`,\n );\n }\n}\n"],"names":["discoverOrganizationMetrics","discoverOrganizationTeamMetrics","discoverEnterpriseMetrics","discoverEnterpriseTeamMetrics"],"mappings":";;;;;;;;;AA+BA,MAAqB,cAAe,CAAA;AAAA,EAGlC,YAA6B,OAAsB,EAAA;AAAtB,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAC3B,IAAA,IAAA,CAAK,KAAQ,GAAA;AAAA,MACX,MAAMA,4CAA4B,CAAA,IAAA,CAAK,OAAO,CAAA;AAAA,MAC9C,MAAMC,oDAAgC,CAAA,IAAA,CAAK,OAAO,CAAA;AAAA,MAClD,MAAMC,wCAA0B,CAAA,IAAA,CAAK,OAAO,CAAA;AAAA,MAC5C,MAAMC,gDAA8B,CAAA,IAAA,CAAK,OAAO;AAAA,KAClD;AAAA;AACF,EATiB,KAAA;AAAA,EAWjB,OAAO,OAAO,OAAsB,EAAA;AAClC,IAAO,OAAA,IAAI,eAAe,OAAO,CAAA;AAAA;AACnC,EAEA,MAAM,QAAW,GAAA;AACf,IAAA,IAAA,CAAK,QAAQ,MAAO,CAAA,IAAA;AAAA,MAClB,CAAA,wCAAA,EAA2C,IAAK,CAAA,KAAA,CAAM,MAAM,CAAA,MAAA;AAAA,KAC9D;AAEA,IAAA,MAAM,eAAe,IAAK,CAAA,KAAA,CAAM,GAAI,CAAA,OAAO,MAAM,KAAU,KAAA;AACzD,MAAI,IAAA;AACF,QAAA,MAAM,IAAK,EAAA;AAAA,eACJ,KAAO,EAAA;AACd,QAAA,MAAM,QAAW,GAAA,IAAA,CAAK,IAAQ,IAAA,CAAA,KAAA,EAAQ,QAAQ,CAAC,CAAA,CAAA;AAC/C,QAAA,IAAA,CAAK,QAAQ,MAAO,CAAA,KAAA;AAAA,UAClB,sCAAsC,QAAQ,CAAA,CAAA,CAAA;AAAA,UAC9C;AAAA,SACF;AAAA;AACF,KACD,CAAA;AAED,IAAM,MAAA,OAAA,CAAQ,IAAI,YAAY,CAAA;AAE9B,IAAA,IAAA,CAAK,QAAQ,MAAO,CAAA,IAAA;AAAA,MAClB,CAAA,kDAAA;AAAA,KACF;AAAA;AAEJ;;;;"}
@@ -49,27 +49,28 @@ const getGithubCredentials = async (config, copilotConfig) => {
49
49
  `Enterprise API for copilot only works with "classic PAT" tokens. No token is configured for "${host}" in the config.`
50
50
  );
51
51
  } else {
52
- credentials.enterprise = {
53
- type: "token",
54
- headers: { Authorization: `Bearer ${githubConfig.token}` },
55
- token: githubConfig.token
56
- };
52
+ credentials.enterprise = githubConfig.token;
57
53
  }
58
54
  }
59
55
  if (organization) {
60
- if (githubConfig.apps) {
61
- const githubCredentialsProvider = integration.DefaultGithubCredentialsProvider.fromIntegrations(integrations);
62
- credentials.organization = await githubCredentialsProvider.getCredentials(
63
- {
64
- url: `https://${host}/${organization}`
65
- }
56
+ if (githubConfig.apps && githubConfig.apps.length > 0) {
57
+ const orgLowerCase = organization.toLowerCase();
58
+ const allowedApp = githubConfig.apps.find(
59
+ (app) => !app.allowedInstallationOwners || app.allowedInstallationOwners.length === 0 || app.allowedInstallationOwners.some(
60
+ (owner) => owner.toLowerCase() === orgLowerCase
61
+ )
66
62
  );
67
- } else if (githubConfig.token) {
63
+ if (!allowedApp) {
64
+ throw new Error(
65
+ `No GitHub App configured for organization "${organization}". Check allowedInstallationOwners in your GitHub integration config.`
66
+ );
67
+ }
68
68
  credentials.organization = {
69
- type: "token",
70
- headers: { Authorization: `Bearer ${githubConfig.token}` },
71
- token: githubConfig.token
69
+ appId: allowedApp.appId,
70
+ privateKey: allowedApp.privateKey
72
71
  };
72
+ } else if (githubConfig.token) {
73
+ credentials.organization = githubConfig.token;
73
74
  } else {
74
75
  throw new Error(
75
76
  `Organization API for copilot works with both classic and fine grained PAT tokens or GitHub apps. No token or app is configured for "${host}" in the config.`
@@ -1 +1 @@
1
- {"version":3,"file":"GithubUtils.cjs.js","sources":["../../src/utils/GithubUtils.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport {\n DefaultGithubCredentialsProvider,\n GithubCredentials,\n ScmIntegrations,\n} from '@backstage/integration';\n\nexport type CopilotCredentials = {\n enterprise?: GithubCredentials;\n organization?: GithubCredentials;\n};\n\nexport type CopilotConfig = {\n host: string;\n enterprise?: string;\n organization?: string;\n apiBaseUrl: string;\n};\n\nexport const getCopilotConfig = (config: Config): CopilotConfig => {\n const host = config.getString('copilot.host');\n const enterprise = config.getOptionalString('copilot.enterprise');\n const organization = config.getOptionalString('copilot.organization');\n\n const integrations = ScmIntegrations.fromConfig(config);\n\n const githubConfig = integrations.github.byHost(host)?.config;\n\n if (!githubConfig) {\n throw new Error(\n `GitHub configuration for host \"${host}\" is missing or incomplete. Please check the integrations configuration section.`,\n );\n }\n\n if (enterprise && !githubConfig.token) {\n throw new Error(\n `Enterprise API for copilot only works with \"classic PAT\" tokens. No token is configured for \"${host}\" in the config.`,\n );\n }\n\n if (organization && !(githubConfig.token || githubConfig.apps)) {\n throw new Error(\n `Organization API for copilot works with both classic and fine grained PAT tokens or GitHub apps. No token or app is configured for \"${host}\" in the config.`,\n );\n }\n\n return {\n host,\n enterprise,\n organization,\n apiBaseUrl: githubConfig.apiBaseUrl ?? 'https://api.github.com',\n };\n};\n\nexport const getGithubCredentials = async (\n config: Config,\n copilotConfig: CopilotConfig,\n): Promise<CopilotCredentials> => {\n const integrations = ScmIntegrations.fromConfig(config);\n const { host, enterprise, organization } = copilotConfig;\n\n const githubConfig = integrations.github.byHost(host)?.config;\n\n if (!githubConfig) {\n throw new Error(\n `GitHub configuration for host \"${host}\" is missing or incomplete.`,\n );\n }\n\n const credentials: CopilotCredentials = {\n enterprise: undefined,\n organization: undefined,\n };\n\n if (enterprise) {\n if (!githubConfig.token) {\n throw new Error(\n `Enterprise API for copilot only works with \"classic PAT\" tokens. No token is configured for \"${host}\" in the config.`,\n );\n } else {\n credentials.enterprise = {\n type: 'token',\n headers: { Authorization: `Bearer ${githubConfig.token}` },\n token: githubConfig.token,\n };\n }\n }\n\n if (organization) {\n if (githubConfig.apps) {\n const githubCredentialsProvider =\n DefaultGithubCredentialsProvider.fromIntegrations(integrations);\n\n credentials.organization = await githubCredentialsProvider.getCredentials(\n {\n url: `https://${host}/${organization}`,\n },\n );\n } else if (githubConfig.token) {\n credentials.organization = {\n type: 'token',\n headers: { Authorization: `Bearer ${githubConfig.token}` },\n token: githubConfig.token,\n };\n } else {\n throw new Error(\n `Organization API for copilot works with both classic and fine grained PAT tokens or GitHub apps. No token or app is configured for \"${host}\" in the config.`,\n );\n }\n }\n\n return credentials;\n};\n"],"names":["ScmIntegrations","DefaultGithubCredentialsProvider"],"mappings":"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"}
1
+ {"version":3,"file":"GithubUtils.cjs.js","sources":["../../src/utils/GithubUtils.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { ScmIntegrations } from '@backstage/integration';\nimport { type StrategyOptions } from '@octokit/auth-app';\n\nexport type OctokitAuthStrategy = StrategyOptions | string;\n\nexport type CopilotCredentials = {\n enterprise?: OctokitAuthStrategy;\n organization?: OctokitAuthStrategy;\n};\n\nexport type CopilotConfig = {\n host: string;\n enterprise?: string;\n organization?: string;\n apiBaseUrl: string;\n};\n\nexport const getCopilotConfig = (config: Config): CopilotConfig => {\n const host = config.getString('copilot.host');\n const enterprise = config.getOptionalString('copilot.enterprise');\n const organization = config.getOptionalString('copilot.organization');\n\n const integrations = ScmIntegrations.fromConfig(config);\n\n const githubConfig = integrations.github.byHost(host)?.config;\n\n if (!githubConfig) {\n throw new Error(\n `GitHub configuration for host \"${host}\" is missing or incomplete. Please check the integrations configuration section.`,\n );\n }\n\n if (enterprise && !githubConfig.token) {\n throw new Error(\n `Enterprise API for copilot only works with \"classic PAT\" tokens. No token is configured for \"${host}\" in the config.`,\n );\n }\n\n if (organization && !(githubConfig.token || githubConfig.apps)) {\n throw new Error(\n `Organization API for copilot works with both classic and fine grained PAT tokens or GitHub apps. No token or app is configured for \"${host}\" in the config.`,\n );\n }\n\n return {\n host,\n enterprise,\n organization,\n apiBaseUrl: githubConfig.apiBaseUrl ?? 'https://api.github.com',\n };\n};\n\nexport const getGithubCredentials = async (\n config: Config,\n copilotConfig: CopilotConfig,\n): Promise<CopilotCredentials> => {\n const integrations = ScmIntegrations.fromConfig(config);\n const { host, enterprise, organization } = copilotConfig;\n\n const githubConfig = integrations.github.byHost(host)?.config;\n\n if (!githubConfig) {\n throw new Error(\n `GitHub configuration for host \"${host}\" is missing or incomplete.`,\n );\n }\n\n const credentials: CopilotCredentials = {\n enterprise: undefined,\n organization: undefined,\n };\n\n if (enterprise) {\n if (!githubConfig.token) {\n throw new Error(\n `Enterprise API for copilot only works with \"classic PAT\" tokens. No token is configured for \"${host}\" in the config.`,\n );\n } else {\n // Use token string for enterprise (PAT tokens) - Octokit will handle it\n credentials.enterprise = githubConfig.token;\n }\n }\n\n if (organization) {\n if (githubConfig.apps && githubConfig.apps.length > 0) {\n // Filter apps that allow this organization (case-insensitive comparison)\n const orgLowerCase = organization.toLowerCase();\n const allowedApp = githubConfig.apps.find(\n app =>\n !app.allowedInstallationOwners ||\n app.allowedInstallationOwners.length === 0 ||\n app.allowedInstallationOwners.some(\n owner => owner.toLowerCase() === orgLowerCase,\n ),\n );\n\n if (!allowedApp) {\n throw new Error(\n `No GitHub App configured for organization \"${organization}\". Check allowedInstallationOwners in your GitHub integration config.`,\n );\n }\n\n // Use app auth strategy for GitHub Apps - handles automatic token refresh\n credentials.organization = {\n appId: allowedApp.appId,\n privateKey: allowedApp.privateKey,\n };\n } else if (githubConfig.token) {\n // Use token string for organization (PAT tokens) - Octokit will handle it\n credentials.organization = githubConfig.token;\n } else {\n throw new Error(\n `Organization API for copilot works with both classic and fine grained PAT tokens or GitHub apps. No token or app is configured for \"${host}\" in the config.`,\n );\n }\n }\n\n return credentials;\n};\n"],"names":["ScmIntegrations"],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ var luxon = require('luxon');
4
+
5
+ function formatDate(value) {
6
+ if (value === null || value === void 0) {
7
+ return "date not available";
8
+ }
9
+ const dateTime = value instanceof Date ? luxon.DateTime.fromJSDate(value).toUTC() : luxon.DateTime.fromJSDate(new Date(value)).toUTC();
10
+ if (!dateTime.isValid) {
11
+ return value instanceof Date ? value.toString() : String(value);
12
+ }
13
+ return dateTime.toISO();
14
+ }
15
+
16
+ exports.formatDate = formatDate;
17
+ //# sourceMappingURL=dateUtils.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dateUtils.cjs.js","sources":["../../src/utils/dateUtils.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { DateTime } from 'luxon';\n\n/**\n * Normalizes date values.\n */\nexport function formatDate(value: string | Date | undefined): string {\n if (value === null || value === undefined) {\n return 'date not available';\n }\n\n const dateTime =\n value instanceof Date\n ? DateTime.fromJSDate(value).toUTC()\n : DateTime.fromJSDate(new Date(value)).toUTC();\n\n if (!dateTime.isValid) {\n return value instanceof Date ? value.toString() : String(value);\n }\n\n return dateTime.toISO();\n}\n"],"names":["DateTime"],"mappings":";;;;AAoBO,SAAS,WAAW,KAA0C,EAAA;AACnE,EAAI,IAAA,KAAA,KAAU,IAAQ,IAAA,KAAA,KAAU,KAAW,CAAA,EAAA;AACzC,IAAO,OAAA,oBAAA;AAAA;AAGT,EAAA,MAAM,WACJ,KAAiB,YAAA,IAAA,GACbA,cAAS,CAAA,UAAA,CAAW,KAAK,CAAE,CAAA,KAAA,EAC3B,GAAAA,cAAA,CAAS,WAAW,IAAI,IAAA,CAAK,KAAK,CAAC,EAAE,KAAM,EAAA;AAEjD,EAAI,IAAA,CAAC,SAAS,OAAS,EAAA;AACrB,IAAA,OAAO,iBAAiB,IAAO,GAAA,KAAA,CAAM,QAAS,EAAA,GAAI,OAAO,KAAK,CAAA;AAAA;AAGhE,EAAA,OAAO,SAAS,KAAM,EAAA;AACxB;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage-community/plugin-copilot-backend",
3
- "version": "0.15.3",
3
+ "version": "0.15.5",
4
4
  "homepage": "https://backstage.io",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.cjs.js",
@@ -49,12 +49,13 @@
49
49
  "@backstage/config": "^1.3.5",
50
50
  "@backstage/errors": "^1.2.7",
51
51
  "@backstage/integration": "^1.18.1",
52
+ "@octokit/auth-app": "^7.1.3",
52
53
  "@octokit/rest": "20.1.2",
53
54
  "@types/express": "*",
54
55
  "express": "^4.17.1",
55
56
  "express-promise-router": "^4.1.0",
56
57
  "knex": "^3.0.0",
57
- "luxon": "^3.5.0",
58
+ "luxon": "^3.7.2",
58
59
  "node-fetch": "^2.6.7",
59
60
  "winston": "^3.2.1",
60
61
  "yn": "^4.0.0",