@backstage-community/plugin-copilot-backend 0.1.6 → 0.3.0

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +37 -17
  3. package/config.d.ts +5 -1
  4. package/dist/client/GithubClient.cjs.js +21 -1
  5. package/dist/client/GithubClient.cjs.js.map +1 -1
  6. package/dist/db/DatabaseHandler.cjs.js +19 -14
  7. package/dist/db/DatabaseHandler.cjs.js.map +1 -1
  8. package/dist/plugin.cjs.js.map +1 -1
  9. package/dist/service/router.cjs.js +33 -22
  10. package/dist/service/router.cjs.js.map +1 -1
  11. package/dist/service/validation/schema.cjs.js +27 -0
  12. package/dist/service/validation/schema.cjs.js.map +1 -0
  13. package/dist/service/validation/validateQuery.cjs.js +17 -0
  14. package/dist/service/validation/validateQuery.cjs.js.map +1 -0
  15. package/dist/task/EnterpriseTask.cjs.js +53 -0
  16. package/dist/task/EnterpriseTask.cjs.js.map +1 -0
  17. package/dist/task/EnterpriseTeamTask.cjs.js +72 -0
  18. package/dist/task/EnterpriseTeamTask.cjs.js.map +1 -0
  19. package/dist/task/OrganizationTask.cjs.js +55 -0
  20. package/dist/task/OrganizationTask.cjs.js.map +1 -0
  21. package/dist/task/OrganizationTeamTask.cjs.js +72 -0
  22. package/dist/task/OrganizationTeamTask.cjs.js.map +1 -0
  23. package/dist/task/TaskManagement.cjs.js +45 -0
  24. package/dist/task/TaskManagement.cjs.js.map +1 -0
  25. package/dist/utils/GithubUtils.cjs.js +4 -5
  26. package/dist/utils/GithubUtils.cjs.js.map +1 -1
  27. package/dist/utils/batchInsert.cjs.js +11 -0
  28. package/dist/utils/batchInsert.cjs.js.map +1 -0
  29. package/dist/utils/metricHelpers.cjs.js +25 -0
  30. package/dist/utils/metricHelpers.cjs.js.map +1 -0
  31. package/migrations/202410191705_add_new_columns.js +48 -0
  32. package/package.json +23 -15
  33. package/dist/task/Scheduler.cjs.js +0 -59
  34. package/dist/task/Scheduler.cjs.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @backstage-community/plugin-copilot-backend
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 53e7191: Backstage version bump to v1.34.2
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [53e7191]
12
+ - @backstage-community/plugin-copilot-common@0.4.0
13
+
14
+ ## 0.2.0
15
+
16
+ ### Minor Changes
17
+
18
+ - 7f17c9f: Introduced support for organizations and team metrics visualization in the Copilot plugin.
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [7f17c9f]
23
+ - @backstage-community/plugin-copilot-common@0.3.0
24
+
3
25
  ## 0.1.6
4
26
 
5
27
  ### Patch Changes
package/README.md CHANGED
@@ -15,7 +15,7 @@ To configure the plugin using the new backend system:
15
15
 
16
16
  const backend = createBackend();
17
17
 
18
- backend.add(import('@backstage-community/plugin-copilot'));
18
+ backend.add(import('@backstage-community/plugin-copilot-backend'));
19
19
 
20
20
  backend.start();
21
21
  ```
@@ -24,17 +24,17 @@ To configure the plugin using the new backend system:
24
24
 
25
25
  To install the plugin using the old method:
26
26
 
27
- 1. Add the `@backstage-community/plugin-copilot` package to your backend:
27
+ 1. Add the `@backstage-community/plugin-copilot-backend` package to your backend:
28
28
 
29
29
  ```sh
30
- yarn --cwd packages/backend add @backstage-community/plugin-copilot
30
+ yarn --cwd packages/backend add @backstage-community/plugin-copilot-backend
31
31
  ```
32
32
 
33
33
  2. In your `packages/backend/src/plugins/copilot.ts` file, add the following code:
34
34
 
35
35
  ```typescript
36
36
  import { TaskScheduleDefinition } from '@backstage/backend-tasks';
37
- import { createRouterFromConfig } from '@backstage-community/plugin-copilot';
37
+ import { createRouterFromConfig } from '@backstage-community/plugin-copilot-backend';
38
38
 
39
39
  export default async function createPlugin(): Promise<void> {
40
40
  const schedule: TaskScheduleDefinition = {
@@ -53,9 +53,7 @@ To install the plugin using the old method:
53
53
  import { createRouterFromConfig } from './plugins/copilot';
54
54
 
55
55
  async function main() {
56
- // Backend setup
57
56
  const env = createEnv('copilot');
58
- // Plugin registration
59
57
  apiRouter.use('/copilot', await createRouterFromConfig(env));
60
58
  }
61
59
  ```
@@ -68,14 +66,36 @@ To configure the GitHub Copilot plugin, you need to set the following environmen
68
66
 
69
67
  - **`copilot.host`**: The host URL for your GitHub Copilot instance (e.g., `github.com` or `github.enterprise.com`).
70
68
  - **`copilot.enterprise`**: The name of your GitHub Enterprise instance (e.g., `my-enterprise`).
69
+ - **`copilot.organization`**: The name of your GitHub Organization (e.g., `my-organization`).
71
70
 
72
71
  These variables are used to configure the plugin and ensure it communicates with the correct GitHub instance.
73
72
 
74
73
  ### GitHub Credentials
75
74
 
76
- **Important:** The GitHub token, which is necessary for authentication, should be managed within your Backstage integrations configuration. The token must be added to your GitHub integration settings, and the plugin will retrieve it through the `GithubCredentialsProvider`.
75
+ **Important:** The GitHub token, necessary for authentication, should be managed within your Backstage integrations configuration. Ensure that your GitHub integration in the Backstage configuration includes the necessary token for the `GithubCredentialsProvider` to function correctly.
77
76
 
78
- Ensure that your GitHub integration in the Backstage configuration includes the necessary token for the `GithubCredentialsProvider` to work correctly.
77
+ ### GitHub Token Scopes
78
+
79
+ To ensure the GitHub Copilot plugin operates correctly within your organization or enterprise, your GitHub access token must include specific scopes. These scopes grant the plugin the necessary permissions to interact with your GitHub organization and manage Copilot usage.
80
+
81
+ #### Required Scopes
82
+
83
+ 1. **List Teams Endpoint**
84
+
85
+ - **Scope Required:** `read:org`
86
+ - **Purpose:** Allows the plugin to list all teams within your GitHub organization.
87
+
88
+ 2. **Copilot Usage**
89
+ - **Scopes Required - enterprise:** `manage_billing:copilot`, `read:enterprise`
90
+ - **Scopes Required - organization:** `manage_billing:copilot`, `read:org`, or `read:enterprise`
91
+ - **Purpose:** Enables the plugin to manage and monitor GitHub Copilot usage within your organization or/and enterprise.
92
+
93
+ #### How to Configure Token Scopes
94
+
95
+ 1. **Generate a Personal Access Token (PAT):**
96
+ - Navigate to [GitHub Personal Access Tokens](https://github.com/settings/tokens).
97
+ - Click on **Generate new token**.
98
+ - Select the scopes according to your needs
79
99
 
80
100
  ### YAML Configuration Example
81
101
 
@@ -90,20 +110,20 @@ copilot:
90
110
  seconds: 15
91
111
  host: YOUR_GITHUB_HOST_HERE
92
112
  enterprise: YOUR_ENTERPRISE_NAME_HERE
93
- ```
94
-
95
- ### Generating GitHub Copilot Token
113
+ organization: YOUR_ORGANIZATION_NAME_HERE
96
114
 
97
- To generate an access token for using GitHub Copilot:
98
-
99
- - Visit [Generate GitHub Access Token](https://github.com/settings/tokens).
100
- - Follow the instructions to create a new token with the `read:enterprise` scope.
115
+ integrations:
116
+ github:
117
+ - host: YOUR_GITHUB_HOST_HERE
118
+ token: YOUR_GENERATED_TOKEN
119
+ ```
101
120
 
102
121
  ### API Documentation
103
122
 
104
- For more details on using the GitHub Copilot API:
123
+ For more details on using the GitHub Copilot and Teams APIs, refer to the following documentation:
105
124
 
106
- - Refer to the [API documentation](https://docs.github.com/en/rest/copilot/copilot-usage?apiVersion=2022-11-28) for comprehensive information on available functionalities.
125
+ - [GitHub Teams API - List Teams](https://docs.github.com/en/rest/teams/teams?apiVersion=2022-11-28#list-teams)
126
+ - [GitHub Copilot API - Usage](https://docs.github.com/en/rest/copilot/copilot-usage?apiVersion=2022-11-28)
107
127
 
108
128
  ## Run
109
129
 
package/config.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2023 The Backstage Authors
2
+ * Copyright 2024 The Backstage Authors
3
3
  *
4
4
  * Licensed under the Apache License, Version 2.0 (the "License");
5
5
  * you may not use this file except in compliance with the License.
@@ -30,6 +30,10 @@ export interface Config {
30
30
  * The name of the GitHub enterprise.
31
31
  */
32
32
  enterprise?: string;
33
+ /**
34
+ * The name of the GitHub organization.
35
+ */
36
+ organization?: string;
33
37
  /**
34
38
  * The host for GitHub Copilot integration.
35
39
  */
@@ -16,10 +16,30 @@ class GithubClient {
16
16
  const info = await GithubUtils.getGithubInfo(config);
17
17
  return new GithubClient(info);
18
18
  }
19
- async getCopilotUsageDataForEnterprise() {
19
+ async fetchEnterpriseCopilotUsage() {
20
20
  const path = `/enterprises/${this.props.enterprise}/copilot/usage`;
21
21
  return this.get(path);
22
22
  }
23
+ async fetchEnterpriseTeamCopilotUsage(teamId) {
24
+ const path = `/enterprises/${this.props.enterprise}/team/${teamId}/copilot/usage`;
25
+ return this.get(path);
26
+ }
27
+ async fetchEnterpriseTeams() {
28
+ const path = `/enterprises/${this.props.enterprise}/teams`;
29
+ return this.get(path);
30
+ }
31
+ async fetchOrganizationCopilotUsage() {
32
+ const path = `/orgs/${this.props.organization}/copilot/usage`;
33
+ return this.get(path);
34
+ }
35
+ async fetchOrganizationTeamCopilotUsage(teamId) {
36
+ const path = `/orgs/${this.props.organization}/team/${teamId}/copilot/usage`;
37
+ return this.get(path);
38
+ }
39
+ async fetchOrganizationTeams() {
40
+ const path = `/orgs/${this.props.organization}/teams`;
41
+ return this.get(path);
42
+ }
23
43
  async get(path) {
24
44
  const response = await fetch__default.default(`${this.props.apiBaseUrl}${path}`, {
25
45
  headers: {
@@ -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 { Metric } from '@backstage-community/plugin-copilot-common';\nimport fetch from 'node-fetch';\nimport { getGithubInfo, GithubInfo } from '../utils/GithubUtils';\n\ninterface GithubApi {\n getCopilotUsageDataForEnterprise: () => Promise<Metric[]>;\n}\n\nexport class GithubClient implements GithubApi {\n constructor(private readonly props: GithubInfo) {}\n\n static async fromConfig(config: Config) {\n const info = await getGithubInfo(config);\n return new GithubClient(info);\n }\n\n async getCopilotUsageDataForEnterprise(): Promise<Metric[]> {\n const path = `/enterprises/${this.props.enterprise}/copilot/usage`;\n return this.get(path);\n }\n\n private async get<T>(path: string): Promise<T> {\n const response = await fetch(`${this.props.apiBaseUrl}${path}`, {\n headers: {\n ...this.props.credentials.headers,\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n },\n });\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n return response.json() as Promise<T>;\n }\n}\n"],"names":["getGithubInfo","fetch","ResponseError"],"mappings":";;;;;;;;;;AA0BO,MAAM,YAAkC,CAAA;AAAA,EAC7C,YAA6B,KAAmB,EAAA;AAAnB,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA,CAAA;AAAA,GAAoB;AAAA,EAEjD,aAAa,WAAW,MAAgB,EAAA;AACtC,IAAM,MAAA,IAAA,GAAO,MAAMA,yBAAA,CAAc,MAAM,CAAA,CAAA;AACvC,IAAO,OAAA,IAAI,aAAa,IAAI,CAAA,CAAA;AAAA,GAC9B;AAAA,EAEA,MAAM,gCAAsD,GAAA;AAC1D,IAAA,MAAM,IAAO,GAAA,CAAA,aAAA,EAAgB,IAAK,CAAA,KAAA,CAAM,UAAU,CAAA,cAAA,CAAA,CAAA;AAClD,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA,CAAA;AAAA,GACtB;AAAA,EAEA,MAAc,IAAO,IAA0B,EAAA;AAC7C,IAAM,MAAA,QAAA,GAAW,MAAMC,sBAAM,CAAA,CAAA,EAAG,KAAK,KAAM,CAAA,UAAU,CAAG,EAAA,IAAI,CAAI,CAAA,EAAA;AAAA,MAC9D,OAAS,EAAA;AAAA,QACP,GAAG,IAAK,CAAA,KAAA,CAAM,WAAY,CAAA,OAAA;AAAA,QAC1B,MAAQ,EAAA,6BAAA;AAAA,QACR,sBAAwB,EAAA,YAAA;AAAA,OAC1B;AAAA,KACD,CAAA,CAAA;AAED,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAM,MAAA,MAAMC,oBAAc,CAAA,YAAA,CAAa,QAAQ,CAAA,CAAA;AAAA,KACjD;AAEA,IAAA,OAAO,SAAS,IAAK,EAAA,CAAA;AAAA,GACvB;AACF;;;;"}
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 { Metric, TeamInfo } from '@backstage-community/plugin-copilot-common';\nimport fetch from 'node-fetch';\nimport { getGithubInfo, GithubInfo } from '../utils/GithubUtils';\n\ninterface GithubApi {\n fetchEnterpriseCopilotUsage: () => Promise<Metric[]>;\n fetchEnterpriseTeamCopilotUsage: (teamId: string) => Promise<Metric[]>;\n fetchEnterpriseTeams: () => Promise<TeamInfo[]>;\n fetchOrganizationCopilotUsage: () => Promise<Metric[]>;\n fetchOrganizationTeamCopilotUsage: (teamId: string) => Promise<Metric[]>;\n fetchOrganizationTeams: () => Promise<TeamInfo[]>;\n}\n\nexport class GithubClient implements GithubApi {\n constructor(private readonly props: GithubInfo) {}\n\n static async fromConfig(config: Config) {\n const info = await getGithubInfo(config);\n return new GithubClient(info);\n }\n\n async fetchEnterpriseCopilotUsage(): Promise<Metric[]> {\n const path = `/enterprises/${this.props.enterprise}/copilot/usage`;\n return this.get(path);\n }\n\n async fetchEnterpriseTeamCopilotUsage(teamId: string): Promise<Metric[]> {\n const path = `/enterprises/${this.props.enterprise}/team/${teamId}/copilot/usage`;\n return this.get(path);\n }\n\n async fetchEnterpriseTeams(): Promise<TeamInfo[]> {\n const path = `/enterprises/${this.props.enterprise}/teams`;\n return this.get(path);\n }\n\n async fetchOrganizationCopilotUsage(): Promise<Metric[]> {\n const path = `/orgs/${this.props.organization}/copilot/usage`;\n return this.get(path);\n }\n\n async fetchOrganizationTeamCopilotUsage(teamId: string): Promise<Metric[]> {\n const path = `/orgs/${this.props.organization}/team/${teamId}/copilot/usage`;\n return this.get(path);\n }\n\n async fetchOrganizationTeams(): Promise<TeamInfo[]> {\n const path = `/orgs/${this.props.organization}/teams`;\n return this.get(path);\n }\n\n private async get<T>(path: string): Promise<T> {\n const response = await fetch(`${this.props.apiBaseUrl}${path}`, {\n headers: {\n ...this.props.credentials.headers,\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n },\n });\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n return response.json() as Promise<T>;\n }\n}\n"],"names":["getGithubInfo","fetch","ResponseError"],"mappings":";;;;;;;;;;AA+BO,MAAM,YAAkC,CAAA;AAAA,EAC7C,YAA6B,KAAmB,EAAA;AAAnB,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AAAA;AAAoB,EAEjD,aAAa,WAAW,MAAgB,EAAA;AACtC,IAAM,MAAA,IAAA,GAAO,MAAMA,yBAAA,CAAc,MAAM,CAAA;AACvC,IAAO,OAAA,IAAI,aAAa,IAAI,CAAA;AAAA;AAC9B,EAEA,MAAM,2BAAiD,GAAA;AACrD,IAAA,MAAM,IAAO,GAAA,CAAA,aAAA,EAAgB,IAAK,CAAA,KAAA,CAAM,UAAU,CAAA,cAAA,CAAA;AAClD,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAM,gCAAgC,MAAmC,EAAA;AACvE,IAAA,MAAM,OAAO,CAAgB,aAAA,EAAA,IAAA,CAAK,KAAM,CAAA,UAAU,SAAS,MAAM,CAAA,cAAA,CAAA;AACjE,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAM,oBAA4C,GAAA;AAChD,IAAA,MAAM,IAAO,GAAA,CAAA,aAAA,EAAgB,IAAK,CAAA,KAAA,CAAM,UAAU,CAAA,MAAA,CAAA;AAClD,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAM,6BAAmD,GAAA;AACvD,IAAA,MAAM,IAAO,GAAA,CAAA,MAAA,EAAS,IAAK,CAAA,KAAA,CAAM,YAAY,CAAA,cAAA,CAAA;AAC7C,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAM,kCAAkC,MAAmC,EAAA;AACzE,IAAA,MAAM,OAAO,CAAS,MAAA,EAAA,IAAA,CAAK,KAAM,CAAA,YAAY,SAAS,MAAM,CAAA,cAAA,CAAA;AAC5D,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAM,sBAA8C,GAAA;AAClD,IAAA,MAAM,IAAO,GAAA,CAAA,MAAA,EAAS,IAAK,CAAA,KAAA,CAAM,YAAY,CAAA,MAAA,CAAA;AAC7C,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAc,IAAO,IAA0B,EAAA;AAC7C,IAAM,MAAA,QAAA,GAAW,MAAMC,sBAAM,CAAA,CAAA,EAAG,KAAK,KAAM,CAAA,UAAU,CAAG,EAAA,IAAI,CAAI,CAAA,EAAA;AAAA,MAC9D,OAAS,EAAA;AAAA,QACP,GAAG,IAAK,CAAA,KAAA,CAAM,WAAY,CAAA,OAAA;AAAA,QAC1B,MAAQ,EAAA,6BAAA;AAAA,QACR,sBAAwB,EAAA;AAAA;AAC1B,KACD,CAAA;AAED,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAM,MAAA,MAAMC,oBAAc,CAAA,YAAA,CAAa,QAAQ,CAAA;AAAA;AAGjD,IAAA,OAAO,SAAS,IAAK,EAAA;AAAA;AAEzB;;;;"}
@@ -20,30 +20,35 @@ class DatabaseHandler {
20
20
  }
21
21
  return new DatabaseHandler(client);
22
22
  }
23
- async getMetricsByPeriod(startDate, endDate) {
24
- const records = await this.db("metrics").whereBetween("day", [
25
- startDate,
26
- endDate
27
- ]);
28
- return records ?? [];
29
- }
30
- async getPeriodRange() {
31
- const minDate = await this.db("metrics").orderBy("day", "asc").first("day");
32
- const maxDate = await this.db("metrics").orderBy("day", "desc").first("day");
23
+ async getPeriodRange(type) {
24
+ const query = this.db("metrics").where("type", type);
25
+ const minDate = await query.orderBy("day", "asc").first("day");
26
+ const maxDate = await query.orderBy("day", "desc").first("day");
33
27
  if (!minDate?.day || !maxDate?.day) return void 0;
34
28
  return { minDate: minDate.day, maxDate: maxDate.day };
35
29
  }
30
+ async getTeams(type, startDate, endDate) {
31
+ const result = await this.db("metrics").where("type", type).whereBetween("day", [startDate, endDate]).whereNotNull("team_name").distinct("team_name").orderBy("team_name", "asc").select("team_name");
32
+ return result.map((x) => x.team_name);
33
+ }
36
34
  async batchInsert(metrics) {
37
- await this.db("metrics").insert(metrics).onConflict("day").ignore();
35
+ await this.db("metrics").insert(metrics).onConflict(["day", "type", "team_name"]).ignore();
38
36
  }
39
- async getMostRecentDayFromMetrics() {
37
+ async getMostRecentDayFromMetrics(type, teamName) {
40
38
  try {
41
- const mostRecent = await this.db("metrics").orderBy("day", "desc").first("day");
42
- return mostRecent ? mostRecent.day : void 0;
39
+ const query = await this.db("metrics").where("type", type).where("team_name", teamName ?? null).orderBy("day", "desc").first("day");
40
+ return query ? query.day : void 0;
43
41
  } catch (e) {
44
42
  return void 0;
45
43
  }
46
44
  }
45
+ async getMetrics(startDate, endDate, type, teamName) {
46
+ console.log(startDate, endDate, type, teamName);
47
+ if (teamName) {
48
+ return await this.db("metrics").where("type", type).where("team_name", teamName).whereBetween("day", [startDate, endDate]);
49
+ }
50
+ return this.db("metrics").where("type", type).whereNull("team_name").whereBetween("day", [startDate, endDate]);
51
+ }
47
52
  }
48
53
 
49
54
  exports.DatabaseHandler = DatabaseHandler;
@@ -1 +1 @@
1
- {"version":3,"file":"DatabaseHandler.cjs.js","sources":["../../src/db/DatabaseHandler.ts"],"sourcesContent":["/*\n * Copyright 2021 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 resolvePackagePath,\n DatabaseService,\n} from '@backstage/backend-plugin-api';\nimport {\n Metric,\n PeriodRange,\n} from '@backstage-community/plugin-copilot-common';\nimport { Knex } from 'knex';\n\nconst migrationsDir = resolvePackagePath(\n '@backstage-community/plugin-copilot-backend',\n 'migrations',\n);\n\ntype Options = {\n database: DatabaseService;\n};\n\nexport type MetricDbRow = Omit<Metric, 'breakdown'> & {\n breakdown: string;\n};\n\nexport class DatabaseHandler {\n static async create(options: Options): Promise<DatabaseHandler> {\n const { database } = options;\n const client = await database.getClient();\n\n if (!database.migrations?.skip) {\n await client.migrate.latest({\n directory: migrationsDir,\n });\n }\n\n return new DatabaseHandler(client);\n }\n\n private constructor(private readonly db: Knex) {}\n\n async getMetricsByPeriod(\n startDate: string,\n endDate: string,\n ): Promise<MetricDbRow[]> {\n const records = await this.db<MetricDbRow>('metrics').whereBetween('day', [\n startDate,\n endDate,\n ]);\n return records ?? [];\n }\n\n async getPeriodRange(): Promise<PeriodRange | undefined> {\n const minDate = await this.db<MetricDbRow>('metrics')\n .orderBy('day', 'asc')\n .first('day');\n const maxDate = await this.db<MetricDbRow>('metrics')\n .orderBy('day', 'desc')\n .first('day');\n\n if (!minDate?.day || !maxDate?.day) return undefined;\n\n return { minDate: minDate.day, maxDate: maxDate.day };\n }\n\n async batchInsert(metrics: MetricDbRow[]): Promise<void> {\n await this.db<MetricDbRow[]>('metrics')\n .insert(metrics)\n .onConflict('day')\n .ignore();\n }\n\n async getMostRecentDayFromMetrics(): Promise<string | undefined> {\n try {\n const mostRecent = await this.db<MetricDbRow>('metrics')\n .orderBy('day', 'desc')\n .first('day');\n\n return mostRecent ? mostRecent.day : undefined;\n } catch (e) {\n return undefined;\n }\n }\n}\n"],"names":["resolvePackagePath"],"mappings":";;;;AA0BA,MAAM,aAAgB,GAAAA,mCAAA;AAAA,EACpB,6CAAA;AAAA,EACA,YAAA;AACF,CAAA,CAAA;AAUO,MAAM,eAAgB,CAAA;AAAA,EAcnB,YAA6B,EAAU,EAAA;AAAV,IAAA,IAAA,CAAA,EAAA,GAAA,EAAA,CAAA;AAAA,GAAW;AAAA,EAbhD,aAAa,OAAO,OAA4C,EAAA;AAC9D,IAAM,MAAA,EAAE,UAAa,GAAA,OAAA,CAAA;AACrB,IAAM,MAAA,MAAA,GAAS,MAAM,QAAA,CAAS,SAAU,EAAA,CAAA;AAExC,IAAI,IAAA,CAAC,QAAS,CAAA,UAAA,EAAY,IAAM,EAAA;AAC9B,MAAM,MAAA,MAAA,CAAO,QAAQ,MAAO,CAAA;AAAA,QAC1B,SAAW,EAAA,aAAA;AAAA,OACZ,CAAA,CAAA;AAAA,KACH;AAEA,IAAO,OAAA,IAAI,gBAAgB,MAAM,CAAA,CAAA;AAAA,GACnC;AAAA,EAIA,MAAM,kBACJ,CAAA,SAAA,EACA,OACwB,EAAA;AACxB,IAAA,MAAM,UAAU,MAAM,IAAA,CAAK,GAAgB,SAAS,CAAA,CAAE,aAAa,KAAO,EAAA;AAAA,MACxE,SAAA;AAAA,MACA,OAAA;AAAA,KACD,CAAA,CAAA;AACD,IAAA,OAAO,WAAW,EAAC,CAAA;AAAA,GACrB;AAAA,EAEA,MAAM,cAAmD,GAAA;AACvD,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,EAAgB,CAAA,SAAS,CACjD,CAAA,OAAA,CAAQ,KAAO,EAAA,KAAK,CACpB,CAAA,KAAA,CAAM,KAAK,CAAA,CAAA;AACd,IAAM,MAAA,OAAA,GAAU,MAAM,IAAA,CAAK,EAAgB,CAAA,SAAS,CACjD,CAAA,OAAA,CAAQ,KAAO,EAAA,MAAM,CACrB,CAAA,KAAA,CAAM,KAAK,CAAA,CAAA;AAEd,IAAA,IAAI,CAAC,OAAS,EAAA,GAAA,IAAO,CAAC,OAAA,EAAS,KAAY,OAAA,KAAA,CAAA,CAAA;AAE3C,IAAA,OAAO,EAAE,OAAS,EAAA,OAAA,CAAQ,GAAK,EAAA,OAAA,EAAS,QAAQ,GAAI,EAAA,CAAA;AAAA,GACtD;AAAA,EAEA,MAAM,YAAY,OAAuC,EAAA;AACvD,IAAM,MAAA,IAAA,CAAK,EAAkB,CAAA,SAAS,CACnC,CAAA,MAAA,CAAO,OAAO,CACd,CAAA,UAAA,CAAW,KAAK,CAAA,CAChB,MAAO,EAAA,CAAA;AAAA,GACZ;AAAA,EAEA,MAAM,2BAA2D,GAAA;AAC/D,IAAI,IAAA;AACF,MAAM,MAAA,UAAA,GAAa,MAAM,IAAA,CAAK,EAAgB,CAAA,SAAS,CACpD,CAAA,OAAA,CAAQ,KAAO,EAAA,MAAM,CACrB,CAAA,KAAA,CAAM,KAAK,CAAA,CAAA;AAEd,MAAO,OAAA,UAAA,GAAa,WAAW,GAAM,GAAA,KAAA,CAAA,CAAA;AAAA,aAC9B,CAAG,EAAA;AACV,MAAO,OAAA,KAAA,CAAA,CAAA;AAAA,KACT;AAAA,GACF;AACF;;;;"}
1
+ {"version":3,"file":"DatabaseHandler.cjs.js","sources":["../../src/db/DatabaseHandler.ts"],"sourcesContent":["/*\n * Copyright 2021 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 resolvePackagePath,\n DatabaseService,\n} from '@backstage/backend-plugin-api';\nimport {\n Metric,\n MetricsType,\n PeriodRange,\n} from '@backstage-community/plugin-copilot-common';\nimport { Knex } from 'knex';\n\nconst migrationsDir = resolvePackagePath(\n '@backstage-community/plugin-copilot-backend',\n 'migrations',\n);\n\ntype Options = {\n database: DatabaseService;\n};\n\nexport type MetricDbRow = Omit<Metric, 'breakdown'> & {\n breakdown: string;\n};\n\nexport class DatabaseHandler {\n static async create(options: Options): Promise<DatabaseHandler> {\n const { database } = options;\n const client = await database.getClient();\n\n if (!database.migrations?.skip) {\n await client.migrate.latest({\n directory: migrationsDir,\n });\n }\n\n return new DatabaseHandler(client);\n }\n\n private constructor(private readonly db: Knex) {}\n\n async getPeriodRange(type: MetricsType): Promise<PeriodRange | undefined> {\n const query = this.db<MetricDbRow>('metrics').where('type', type);\n\n const minDate = await query.orderBy('day', 'asc').first('day');\n const maxDate = await query.orderBy('day', 'desc').first('day');\n\n if (!minDate?.day || !maxDate?.day) return undefined;\n\n return { minDate: minDate.day, maxDate: maxDate.day };\n }\n\n async getTeams(\n type: MetricsType,\n startDate: string,\n endDate: string,\n ): Promise<Array<string | undefined>> {\n const result = await this.db<MetricDbRow>('metrics')\n .where('type', type)\n .whereBetween('day', [startDate, endDate])\n .whereNotNull('team_name')\n .distinct('team_name')\n .orderBy('team_name', 'asc')\n .select('team_name');\n\n return result.map(x => x.team_name);\n }\n\n async batchInsert(metrics: MetricDbRow[]): Promise<void> {\n await this.db<MetricDbRow[]>('metrics')\n .insert(metrics)\n .onConflict(['day', 'type', 'team_name'])\n .ignore();\n }\n\n async getMostRecentDayFromMetrics(\n type: MetricsType,\n teamName?: string,\n ): Promise<string | undefined> {\n try {\n const query = await this.db<MetricDbRow>('metrics')\n .where('type', type)\n .where('team_name', teamName ?? null)\n .orderBy('day', 'desc')\n .first('day');\n return query ? query.day : undefined;\n } catch (e) {\n return undefined;\n }\n }\n\n async getMetrics(\n startDate: string,\n endDate: string,\n type: MetricsType,\n teamName?: string,\n ): Promise<MetricDbRow[]> {\n console.log(startDate, endDate, type, teamName);\n if (teamName) {\n return await this.db<MetricDbRow>('metrics')\n .where('type', type)\n .where('team_name', teamName)\n .whereBetween('day', [startDate, endDate]);\n }\n return this.db<MetricDbRow>('metrics')\n .where('type', type)\n .whereNull('team_name')\n .whereBetween('day', [startDate, endDate]);\n }\n}\n"],"names":["resolvePackagePath"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.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 coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouterFromConfig } from './service/router';\n\n/**\n * Backend plugin for Copilot.\n *\n * @public\n */\nexport const copilotPlugin = createBackendPlugin({\n pluginId: 'copilot',\n register(env) {\n env.registerInit({\n deps: {\n httpRouter: coreServices.httpRouter,\n logger: coreServices.logger,\n database: coreServices.database,\n scheduler: coreServices.scheduler,\n config: coreServices.rootConfig,\n },\n async init({ httpRouter, logger, database, scheduler, config }) {\n httpRouter.use(\n await createRouterFromConfig({\n logger,\n database,\n scheduler,\n config,\n }),\n );\n httpRouter.addAuthPolicy({\n path: '/health',\n allow: 'unauthenticated',\n });\n },\n });\n },\n});\n"],"names":["createBackendPlugin","coreServices","createRouterFromConfig"],"mappings":";;;;;AA0BO,MAAM,gBAAgBA,oCAAoB,CAAA;AAAA,EAC/C,QAAU,EAAA,SAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,YAAYC,6BAAa,CAAA,UAAA;AAAA,QACzB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,QAAQA,6BAAa,CAAA,UAAA;AAAA,OACvB;AAAA,MACA,MAAM,KAAK,EAAE,UAAA,EAAY,QAAQ,QAAU,EAAA,SAAA,EAAW,QAAU,EAAA;AAC9D,QAAW,UAAA,CAAA,GAAA;AAAA,UACT,MAAMC,6BAAuB,CAAA;AAAA,YAC3B,MAAA;AAAA,YACA,QAAA;AAAA,YACA,SAAA;AAAA,YACA,MAAA;AAAA,WACD,CAAA;AAAA,SACH,CAAA;AACA,QAAA,UAAA,CAAW,aAAc,CAAA;AAAA,UACvB,IAAM,EAAA,SAAA;AAAA,UACN,KAAO,EAAA,iBAAA;AAAA,SACR,CAAA,CAAA;AAAA,OACH;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AACF,CAAC;;;;"}
1
+ {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.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 coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouterFromConfig } from './service/router';\n\n/**\n * Backend plugin for Copilot.\n *\n * @public\n */\nexport const copilotPlugin = createBackendPlugin({\n pluginId: 'copilot',\n register(env) {\n env.registerInit({\n deps: {\n httpRouter: coreServices.httpRouter,\n logger: coreServices.logger,\n database: coreServices.database,\n scheduler: coreServices.scheduler,\n config: coreServices.rootConfig,\n },\n async init({ httpRouter, logger, database, scheduler, config }) {\n httpRouter.use(\n await createRouterFromConfig({\n logger,\n database,\n scheduler,\n config,\n }),\n );\n httpRouter.addAuthPolicy({\n path: '/health',\n allow: 'unauthenticated',\n });\n },\n });\n },\n});\n"],"names":["createBackendPlugin","coreServices","createRouterFromConfig"],"mappings":";;;;;AA0BO,MAAM,gBAAgBA,oCAAoB,CAAA;AAAA,EAC/C,QAAU,EAAA,SAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,YAAYC,6BAAa,CAAA,UAAA;AAAA,QACzB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,UAAUA,6BAAa,CAAA,QAAA;AAAA,QACvB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,QAAQA,6BAAa,CAAA;AAAA,OACvB;AAAA,MACA,MAAM,KAAK,EAAE,UAAA,EAAY,QAAQ,QAAU,EAAA,SAAA,EAAW,QAAU,EAAA;AAC9D,QAAW,UAAA,CAAA,GAAA;AAAA,UACT,MAAMC,6BAAuB,CAAA;AAAA,YAC3B,MAAA;AAAA,YACA,QAAA;AAAA,YACA,SAAA;AAAA,YACA;AAAA,WACD;AAAA,SACH;AACA,QAAA,UAAA,CAAW,aAAc,CAAA;AAAA,UACvB,IAAM,EAAA,SAAA;AAAA,UACN,KAAO,EAAA;AAAA,SACR,CAAA;AAAA;AACH,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
@@ -4,10 +4,12 @@ var express = require('express');
4
4
  var Router = require('express-promise-router');
5
5
  var rootHttpRouter = require('@backstage/backend-defaults/rootHttpRouter');
6
6
  var backendPluginApi = require('@backstage/backend-plugin-api');
7
+ var errors = require('@backstage/errors');
7
8
  var DatabaseHandler = require('../db/DatabaseHandler.cjs.js');
8
- var Scheduler = require('../task/Scheduler.cjs.js');
9
+ var TaskManagement = require('../task/TaskManagement.cjs.js');
9
10
  var GithubClient = require('../client/GithubClient.cjs.js');
10
- var luxon = require('luxon');
11
+ var validateQuery = require('./validation/validateQuery.cjs.js');
12
+ var schema = require('./validation/schema.cjs.js');
11
13
 
12
14
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
13
15
 
@@ -40,7 +42,7 @@ async function createRouter(routerOptions, pluginOptions) {
40
42
  await scheduler.scheduleTask({
41
43
  id: "copilot-metrics",
42
44
  ...schedule ?? defaultSchedule,
43
- fn: async () => await Scheduler.default.create({ db, logger, api, config }).run()
45
+ fn: async () => await TaskManagement.default.create({ db, logger, api, config }).runAsync()
44
46
  });
45
47
  const router = Router__default.default();
46
48
  router.use(express__default.default.json());
@@ -48,29 +50,38 @@ async function createRouter(routerOptions, pluginOptions) {
48
50
  logger.info("PONG!");
49
51
  response.json({ status: "ok" });
50
52
  });
51
- router.get("/metrics", async (request, response) => {
52
- const { startDate, endDate } = request.query;
53
- if (typeof startDate !== "string" || typeof endDate !== "string") {
54
- return response.status(400).json("Invalid query parameters");
53
+ router.get(
54
+ "/metrics",
55
+ validateQuery.validateQuery(schema.metricsQuerySchema),
56
+ async (req, res) => {
57
+ const { startDate, endDate, type, team } = req.query;
58
+ const result = await db.getMetrics(startDate, endDate, type, team);
59
+ const metrics = result.map((metric) => ({
60
+ ...metric,
61
+ breakdown: JSON.parse(metric.breakdown)
62
+ }));
63
+ return res.json(metrics);
55
64
  }
56
- const parsedStartDate = luxon.DateTime.fromISO(startDate);
57
- const parsedEndDate = luxon.DateTime.fromISO(endDate);
58
- if (!parsedStartDate.isValid || !parsedEndDate.isValid) {
59
- return response.status(400).json("Invalid date format");
65
+ );
66
+ router.get(
67
+ "/metrics/period-range",
68
+ validateQuery.validateQuery(schema.periodRangeQuerySchema),
69
+ async (req, res) => {
70
+ const { type } = req.query;
71
+ const result = await db.getPeriodRange(type);
72
+ if (!result) {
73
+ throw new errors.NotFoundError();
74
+ }
75
+ return res.json(result);
60
76
  }
61
- const result = await db.getMetricsByPeriod(startDate, endDate);
62
- const metrics = result.map((metric) => ({
63
- ...metric,
64
- breakdown: JSON.parse(metric.breakdown)
65
- }));
66
- return response.json(metrics);
67
- });
68
- router.get("/metrics/period-range", async (_, response) => {
69
- const result = await db.getPeriodRange();
77
+ );
78
+ router.get("/teams", validateQuery.validateQuery(schema.teamQuerySchema), async (req, res) => {
79
+ const { type, startDate, endDate } = req.query;
80
+ const result = await db.getTeams(type, startDate, endDate);
70
81
  if (!result) {
71
- return response.status(400).json("No available data");
82
+ throw new errors.NotFoundError();
72
83
  }
73
- return response.json(result);
84
+ return res.json(result);
74
85
  });
75
86
  router.use(rootHttpRouter.MiddlewareFactory.create({ config, logger }).error);
76
87
  return router;
@@ -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 { Metric } from '@backstage-community/plugin-copilot-common';\nimport { DatabaseHandler } from '../db/DatabaseHandler';\nimport Scheduler from '../task/Scheduler';\nimport { GithubClient } from '../client/GithubClient';\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 () => await Scheduler.create({ db, logger, api, config }).run(),\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('/metrics', async (request, response) => {\n const { startDate, endDate } = request.query;\n\n if (typeof startDate !== 'string' || typeof endDate !== 'string') {\n return response.status(400).json('Invalid query parameters');\n }\n\n const parsedStartDate = DateTime.fromISO(startDate);\n const parsedEndDate = DateTime.fromISO(endDate);\n\n if (!parsedStartDate.isValid || !parsedEndDate.isValid) {\n return response.status(400).json('Invalid date format');\n }\n\n const result = await db.getMetricsByPeriod(startDate, endDate);\n\n const metrics: Metric[] = result.map(metric => ({\n ...metric,\n breakdown: JSON.parse(metric.breakdown),\n }));\n\n return response.json(metrics);\n });\n\n router.get('/metrics/period-range', async (_, response) => {\n const result = await db.getPeriodRange();\n\n if (!result) {\n return response.status(400).json('No available data');\n }\n\n return response.json(result);\n });\n\n router.use(MiddlewareFactory.create({ config, logger }).error);\n return router;\n}\n"],"names":["readSchedulerServiceTaskScheduleDefinitionFromConfig","DatabaseHandler","GithubClient","Scheduler","Router","express","DateTime","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 { Metric } 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';\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\n const result = await db.getMetrics(startDate, endDate, type, team);\n\n const metrics: Metric[] = result.map(metric => ({\n ...metric,\n breakdown: JSON.parse(metric.breakdown),\n }));\n\n return res.json(metrics);\n },\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 result = await db.getPeriodRange(type);\n\n if (!result) {\n throw new NotFoundError();\n }\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","periodRangeQuerySchema","NotFoundError","teamQuerySchema","MiddlewareFactory"],"mappings":";;;;;;;;;;;;;;;;;;AAgFA,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,MAAM,CAAA;AAEhD,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;AAE/C,MAAA,MAAM,SAAS,MAAM,EAAA,CAAG,WAAW,SAAW,EAAA,OAAA,EAAS,MAAM,IAAI,CAAA;AAEjE,MAAM,MAAA,OAAA,GAAoB,MAAO,CAAA,GAAA,CAAI,CAAW,MAAA,MAAA;AAAA,QAC9C,GAAG,MAAA;AAAA,QACH,SAAW,EAAA,IAAA,CAAK,KAAM,CAAA,MAAA,CAAO,SAAS;AAAA,OACtC,CAAA,CAAA;AAEF,MAAO,OAAA,GAAA,CAAI,KAAK,OAAO,CAAA;AAAA;AACzB,GACF;AAEA,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,uBAAA;AAAA,IACAD,4BAAcE,6BAAsB,CAAA;AAAA,IACpC,OAAO,KAAK,GAAQ,KAAA;AAClB,MAAM,MAAA,EAAE,IAAK,EAAA,GAAI,GAAI,CAAA,KAAA;AACrB,MAAA,MAAM,MAAS,GAAA,MAAM,EAAG,CAAA,cAAA,CAAe,IAAI,CAAA;AAE3C,MAAA,IAAI,CAAC,MAAQ,EAAA;AACX,QAAA,MAAM,IAAIC,oBAAc,EAAA;AAAA;AAG1B,MAAO,OAAA,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA;AACxB,GACF;AAEA,EAAA,MAAA,CAAO,IAAI,QAAU,EAAAH,2BAAA,CAAcI,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,IAAID,oBAAc,EAAA;AAAA;AAG1B,IAAO,OAAA,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA,GACvB,CAAA;AAED,EAAO,MAAA,CAAA,GAAA,CAAIE,iCAAkB,MAAO,CAAA,EAAE,QAAQ,MAAO,EAAC,EAAE,KAAK,CAAA;AAC7D,EAAO,OAAA,MAAA;AACT;;;;"}
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+
5
+ const metricsTypeSchema = zod.z.enum(["enterprise", "organization"]);
6
+ const isoDateSchema = zod.z.string().refine((date) => !isNaN(Date.parse(date)), {
7
+ message: "Invalid date format"
8
+ });
9
+ const metricsQuerySchema = zod.z.object({
10
+ startDate: isoDateSchema,
11
+ endDate: isoDateSchema,
12
+ type: metricsTypeSchema,
13
+ team: zod.z.string().optional()
14
+ });
15
+ const periodRangeQuerySchema = zod.z.object({
16
+ type: metricsTypeSchema
17
+ });
18
+ const teamQuerySchema = zod.z.object({
19
+ startDate: isoDateSchema,
20
+ endDate: isoDateSchema,
21
+ type: metricsTypeSchema
22
+ });
23
+
24
+ exports.metricsQuerySchema = metricsQuerySchema;
25
+ exports.periodRangeQuerySchema = periodRangeQuerySchema;
26
+ exports.teamQuerySchema = teamQuerySchema;
27
+ //# sourceMappingURL=schema.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.cjs.js","sources":["../../../src/service/validation/schema.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 { z } from 'zod';\nimport { MetricsType } from '@backstage-community/plugin-copilot-common';\n\nconst metricsTypeSchema = z.enum(['enterprise', 'organization']);\n\nconst isoDateSchema = z.string().refine(date => !isNaN(Date.parse(date)), {\n message: 'Invalid date format',\n});\n\nexport type MetricsQuery = {\n startDate: string;\n endDate: string;\n type: MetricsType;\n team?: string;\n};\n\nexport type PeriodRangeQuery = {\n type: MetricsType;\n};\n\nexport type TeamQuery = {\n type: MetricsType;\n startDate: string;\n endDate: string;\n};\n\nexport const metricsQuerySchema = z.object({\n startDate: isoDateSchema,\n endDate: isoDateSchema,\n type: metricsTypeSchema,\n team: z.string().optional(),\n});\n\nexport const periodRangeQuerySchema = z.object({\n type: metricsTypeSchema,\n});\n\nexport const teamQuerySchema = z.object({\n startDate: isoDateSchema,\n endDate: isoDateSchema,\n type: metricsTypeSchema,\n});\n"],"names":["z"],"mappings":";;;;AAkBA,MAAM,oBAAoBA,KAAE,CAAA,IAAA,CAAK,CAAC,YAAA,EAAc,cAAc,CAAC,CAAA;AAE/D,MAAM,aAAgB,GAAAA,KAAA,CAAE,MAAO,EAAA,CAAE,MAAO,CAAA,CAAA,IAAA,KAAQ,CAAC,KAAA,CAAM,IAAK,CAAA,KAAA,CAAM,IAAI,CAAC,CAAG,EAAA;AAAA,EACxE,OAAS,EAAA;AACX,CAAC,CAAA;AAmBY,MAAA,kBAAA,GAAqBA,MAAE,MAAO,CAAA;AAAA,EACzC,SAAW,EAAA,aAAA;AAAA,EACX,OAAS,EAAA,aAAA;AAAA,EACT,IAAM,EAAA,iBAAA;AAAA,EACN,IAAM,EAAAA,KAAA,CAAE,MAAO,EAAA,CAAE,QAAS;AAC5B,CAAC;AAEY,MAAA,sBAAA,GAAyBA,MAAE,MAAO,CAAA;AAAA,EAC7C,IAAM,EAAA;AACR,CAAC;AAEY,MAAA,eAAA,GAAkBA,MAAE,MAAO,CAAA;AAAA,EACtC,SAAW,EAAA,aAAA;AAAA,EACX,OAAS,EAAA,aAAA;AAAA,EACT,IAAM,EAAA;AACR,CAAC;;;;;;"}
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+
5
+ function validateQuery(schema) {
6
+ return (req, _res, next) => {
7
+ const { error, data } = schema.safeParse(req.query);
8
+ if (error) {
9
+ throw new errors.InputError(error.errors[0].message, error);
10
+ }
11
+ req.query = data;
12
+ return next();
13
+ };
14
+ }
15
+
16
+ exports.validateQuery = validateQuery;
17
+ //# sourceMappingURL=validateQuery.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateQuery.cjs.js","sources":["../../../src/service/validation/validateQuery.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 { InputError } from '@backstage/errors';\nimport { NextFunction, Request, Response } from 'express';\nimport { ZodSchema } from 'zod';\n\nexport function validateQuery(schema: ZodSchema) {\n return (req: Request, _res: Response, next: NextFunction) => {\n const { error, data } = schema.safeParse(req.query);\n\n if (error) {\n throw new InputError(error.errors[0].message, error);\n }\n\n req.query = data;\n\n return next();\n };\n}\n"],"names":["InputError"],"mappings":";;;;AAmBO,SAAS,cAAc,MAAmB,EAAA;AAC/C,EAAO,OAAA,CAAC,GAAc,EAAA,IAAA,EAAgB,IAAuB,KAAA;AAC3D,IAAA,MAAM,EAAE,KAAO,EAAA,IAAA,KAAS,MAAO,CAAA,SAAA,CAAU,IAAI,KAAK,CAAA;AAElD,IAAA,IAAI,KAAO,EAAA;AACT,MAAA,MAAM,IAAIA,iBAAW,CAAA,KAAA,CAAM,OAAO,CAAC,CAAA,CAAE,SAAS,KAAK,CAAA;AAAA;AAGrD,IAAA,GAAA,CAAI,KAAQ,GAAA,IAAA;AAEZ,IAAA,OAAO,IAAK,EAAA;AAAA,GACd;AACF;;;;"}
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ var batchInsert = require('../utils/batchInsert.cjs.js');
4
+ var metricHelpers = require('../utils/metricHelpers.cjs.js');
5
+
6
+ async function discoverEnterpriseMetrics({
7
+ api,
8
+ logger,
9
+ db,
10
+ config
11
+ }) {
12
+ if (!config.getOptionalString("copilot.enterprise")) {
13
+ logger.info(
14
+ "[discoverEnterpriseMetrics] Skipping: Enterprise configuration not found."
15
+ );
16
+ return;
17
+ }
18
+ const type = "enterprise";
19
+ try {
20
+ const metrics = await api.fetchEnterpriseCopilotUsage();
21
+ logger.info(
22
+ `[discoverEnterpriseMetrics] Fetched ${metrics.length} metrics`
23
+ );
24
+ const lastDay = await db.getMostRecentDayFromMetrics(type);
25
+ logger.info(`[discoverEnterpriseMetrics] Found last day: ${lastDay}`);
26
+ const newMetrics = metricHelpers.filterNewMetrics(metrics, lastDay);
27
+ logger.info(
28
+ `[discoverEnterpriseMetrics] Found ${newMetrics.length} new metrics to insert`
29
+ );
30
+ if (newMetrics.length > 0) {
31
+ await batchInsert.batchInsertInChunks(
32
+ metricHelpers.prepareMetricsForInsert(newMetrics, type),
33
+ 30,
34
+ async (chunk) => {
35
+ await db.batchInsert(chunk);
36
+ }
37
+ );
38
+ logger.info(
39
+ "[discoverEnterpriseMetrics] Inserted new metrics into the database"
40
+ );
41
+ } else {
42
+ logger.info("[discoverEnterpriseMetrics] No new metrics found to insert");
43
+ }
44
+ } catch (error) {
45
+ logger.error(
46
+ `[discoverEnterpriseMetrics] An error occurred while processing Github Copilot metrics: ${error}`
47
+ );
48
+ throw error;
49
+ }
50
+ }
51
+
52
+ exports.discoverEnterpriseMetrics = discoverEnterpriseMetrics;
53
+ //# sourceMappingURL=EnterpriseTask.cjs.js.map
@@ -0,0 +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 { MetricsType } from '@backstage-community/plugin-copilot-common';\nimport { MetricDbRow } from '../db/DatabaseHandler';\nimport { batchInsertInChunks } from '../utils/batchInsert';\nimport {\n filterNewMetrics,\n prepareMetricsForInsert,\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 metrics = await api.fetchEnterpriseCopilotUsage();\n logger.info(\n `[discoverEnterpriseMetrics] Fetched ${metrics.length} metrics`,\n );\n\n const lastDay = await db.getMostRecentDayFromMetrics(type);\n logger.info(`[discoverEnterpriseMetrics] Found last day: ${lastDay}`);\n\n const newMetrics = filterNewMetrics(metrics, lastDay);\n logger.info(\n `[discoverEnterpriseMetrics] Found ${newMetrics.length} new metrics to insert`,\n );\n\n if (newMetrics.length > 0) {\n await batchInsertInChunks<MetricDbRow>(\n prepareMetricsForInsert(newMetrics, type),\n 30,\n async (chunk: MetricDbRow[]) => {\n await db.batchInsert(chunk);\n },\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: ${error}`,\n );\n throw error;\n }\n}\n"],"names":["filterNewMetrics","batchInsertInChunks","prepareMetricsForInsert"],"mappings":";;;;;AAwBA,eAAsB,yBAA0B,CAAA;AAAA,EAC9C,GAAA;AAAA,EACA,MAAA;AAAA,EACA,EAAA;AAAA,EACA;AACF,CAA+B,EAAA;AAC7B,EAAA,IAAI,CAAC,MAAA,CAAO,iBAAkB,CAAA,oBAAoB,CAAG,EAAA;AACnD,IAAO,MAAA,CAAA,IAAA;AAAA,MACL;AAAA,KACF;AACA,IAAA;AAAA;AAGF,EAAA,MAAM,IAAoB,GAAA,YAAA;AAE1B,EAAI,IAAA;AACF,IAAM,MAAA,OAAA,GAAU,MAAM,GAAA,CAAI,2BAA4B,EAAA;AACtD,IAAO,MAAA,CAAA,IAAA;AAAA,MACL,CAAA,oCAAA,EAAuC,QAAQ,MAAM,CAAA,QAAA;AAAA,KACvD;AAEA,IAAA,MAAM,OAAU,GAAA,MAAM,EAAG,CAAA,2BAAA,CAA4B,IAAI,CAAA;AACzD,IAAO,MAAA,CAAA,IAAA,CAAK,CAA+C,4CAAA,EAAA,OAAO,CAAE,CAAA,CAAA;AAEpE,IAAM,MAAA,UAAA,GAAaA,8BAAiB,CAAA,OAAA,EAAS,OAAO,CAAA;AACpD,IAAO,MAAA,CAAA,IAAA;AAAA,MACL,CAAA,kCAAA,EAAqC,WAAW,MAAM,CAAA,sBAAA;AAAA,KACxD;AAEA,IAAI,IAAA,UAAA,CAAW,SAAS,CAAG,EAAA;AACzB,MAAM,MAAAC,+BAAA;AAAA,QACJC,qCAAA,CAAwB,YAAY,IAAI,CAAA;AAAA,QACxC,EAAA;AAAA,QACA,OAAO,KAAyB,KAAA;AAC9B,UAAM,MAAA,EAAA,CAAG,YAAY,KAAK,CAAA;AAAA;AAC5B,OACF;AACA,MAAO,MAAA,CAAA,IAAA;AAAA,QACL;AAAA,OACF;AAAA,KACK,MAAA;AACL,MAAA,MAAA,CAAO,KAAK,4DAA4D,CAAA;AAAA;AAC1E,WACO,KAAO,EAAA;AACd,IAAO,MAAA,CAAA,KAAA;AAAA,MACL,0FAA0F,KAAK,CAAA;AAAA,KACjG;AACA,IAAM,MAAA,KAAA;AAAA;AAEV;;;;"}
@@ -0,0 +1,72 @@
1
+ 'use strict';
2
+
3
+ var batchInsert = require('../utils/batchInsert.cjs.js');
4
+ var metricHelpers = require('../utils/metricHelpers.cjs.js');
5
+
6
+ async function discoverEnterpriseTeamMetrics({
7
+ api,
8
+ logger,
9
+ db,
10
+ config
11
+ }) {
12
+ if (!config.getOptionalString("copilot.enterprise")) {
13
+ logger.info(
14
+ "[discoverEnterpriseTeamMetrics] Skipping: Enterprise configuration not found."
15
+ );
16
+ return;
17
+ }
18
+ const type = "enterprise";
19
+ try {
20
+ const teams = await api.fetchEnterpriseTeams();
21
+ logger.info(
22
+ `[discoverEnterpriseTeamMetrics] Fetched ${teams.length} teams`
23
+ );
24
+ for (const team of teams) {
25
+ try {
26
+ logger.info(
27
+ `[discoverEnterpriseTeamMetrics] Fetching metrics for team: ${team.slug}`
28
+ );
29
+ const metrics = await api.fetchEnterpriseTeamCopilotUsage(team.slug);
30
+ logger.info(
31
+ `[discoverEnterpriseTeamMetrics] Fetched ${metrics.length} metrics for team: ${team.slug}`
32
+ );
33
+ const lastDay = await db.getMostRecentDayFromMetrics(type, team.slug);
34
+ logger.info(
35
+ `[discoverEnterpriseTeamMetrics] Found last processed day for team ${team.slug}: ${lastDay}`
36
+ );
37
+ const newMetrics = metricHelpers.filterNewMetrics(metrics, lastDay);
38
+ logger.info(
39
+ `[discoverEnterpriseTeamMetrics] Found ${newMetrics.length} new metrics for team ${team.slug} to insert`
40
+ );
41
+ if (newMetrics.length > 0) {
42
+ await batchInsert.batchInsertInChunks(
43
+ metricHelpers.prepareMetricsForInsert(newMetrics, type, team.slug),
44
+ 30,
45
+ async (chunk) => {
46
+ await db.batchInsert(chunk);
47
+ }
48
+ );
49
+ logger.info(
50
+ `[discoverEnterpriseTeamMetrics] Successfully inserted new metrics for team ${team.slug} into the database`
51
+ );
52
+ } else {
53
+ logger.info(
54
+ `[discoverEnterpriseTeamMetrics] No new metrics found for team ${team.slug} to insert`
55
+ );
56
+ }
57
+ } catch (error) {
58
+ logger.error(
59
+ `[discoverEnterpriseTeamMetrics] Error processing metrics for team ${team.slug}: ${error}`
60
+ );
61
+ }
62
+ }
63
+ } catch (error) {
64
+ logger.error(
65
+ `[discoverEnterpriseTeamMetrics] Error fetching teams: ${error}`
66
+ );
67
+ throw error;
68
+ }
69
+ }
70
+
71
+ exports.discoverEnterpriseTeamMetrics = discoverEnterpriseTeamMetrics;
72
+ //# sourceMappingURL=EnterpriseTeamTask.cjs.js.map
@@ -0,0 +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 { MetricsType } from '@backstage-community/plugin-copilot-common';\nimport { MetricDbRow } from '../db/DatabaseHandler';\nimport { batchInsertInChunks } from '../utils/batchInsert';\nimport {\n filterNewMetrics,\n prepareMetricsForInsert,\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 for (const team of teams) {\n try {\n logger.info(\n `[discoverEnterpriseTeamMetrics] Fetching metrics for team: ${team.slug}`,\n );\n\n const metrics = await api.fetchEnterpriseTeamCopilotUsage(team.slug);\n logger.info(\n `[discoverEnterpriseTeamMetrics] Fetched ${metrics.length} metrics for team: ${team.slug}`,\n );\n\n const lastDay = await db.getMostRecentDayFromMetrics(type, team.slug);\n logger.info(\n `[discoverEnterpriseTeamMetrics] Found last processed day for team ${team.slug}: ${lastDay}`,\n );\n\n const newMetrics = filterNewMetrics(metrics, lastDay);\n logger.info(\n `[discoverEnterpriseTeamMetrics] Found ${newMetrics.length} new metrics for team ${team.slug} to insert`,\n );\n\n if (newMetrics.length > 0) {\n await batchInsertInChunks<MetricDbRow>(\n prepareMetricsForInsert(newMetrics, type, team.slug),\n 30,\n async (chunk: MetricDbRow[]) => {\n await db.batchInsert(chunk);\n },\n );\n logger.info(\n `[discoverEnterpriseTeamMetrics] Successfully inserted new metrics for team ${team.slug} into the database`,\n );\n } else {\n logger.info(\n `[discoverEnterpriseTeamMetrics] No new metrics found for team ${team.slug} to insert`,\n );\n }\n } catch (error) {\n logger.error(\n `[discoverEnterpriseTeamMetrics] Error processing metrics for team ${team.slug}: ${error}`,\n );\n }\n }\n } catch (error) {\n logger.error(\n `[discoverEnterpriseTeamMetrics] Error fetching teams: ${error}`,\n );\n throw error;\n }\n}\n"],"names":["filterNewMetrics","batchInsertInChunks","prepareMetricsForInsert"],"mappings":";;;;;AAwBA,eAAsB,6BAA8B,CAAA;AAAA,EAClD,GAAA;AAAA,EACA,MAAA;AAAA,EACA,EAAA;AAAA,EACA;AACF,CAA+B,EAAA;AAC7B,EAAA,IAAI,CAAC,MAAA,CAAO,iBAAkB,CAAA,oBAAoB,CAAG,EAAA;AACnD,IAAO,MAAA,CAAA,IAAA;AAAA,MACL;AAAA,KACF;AACA,IAAA;AAAA;AAGF,EAAA,MAAM,IAAoB,GAAA,YAAA;AAE1B,EAAI,IAAA;AACF,IAAM,MAAA,KAAA,GAAQ,MAAM,GAAA,CAAI,oBAAqB,EAAA;AAC7C,IAAO,MAAA,CAAA,IAAA;AAAA,MACL,CAAA,wCAAA,EAA2C,MAAM,MAAM,CAAA,MAAA;AAAA,KACzD;AAEA,IAAA,KAAA,MAAW,QAAQ,KAAO,EAAA;AACxB,MAAI,IAAA;AACF,QAAO,MAAA,CAAA,IAAA;AAAA,UACL,CAAA,2DAAA,EAA8D,KAAK,IAAI,CAAA;AAAA,SACzE;AAEA,QAAA,MAAM,OAAU,GAAA,MAAM,GAAI,CAAA,+BAAA,CAAgC,KAAK,IAAI,CAAA;AACnE,QAAO,MAAA,CAAA,IAAA;AAAA,UACL,CAA2C,wCAAA,EAAA,OAAA,CAAQ,MAAM,CAAA,mBAAA,EAAsB,KAAK,IAAI,CAAA;AAAA,SAC1F;AAEA,QAAA,MAAM,UAAU,MAAM,EAAA,CAAG,2BAA4B,CAAA,IAAA,EAAM,KAAK,IAAI,CAAA;AACpE,QAAO,MAAA,CAAA,IAAA;AAAA,UACL,CAAqE,kEAAA,EAAA,IAAA,CAAK,IAAI,CAAA,EAAA,EAAK,OAAO,CAAA;AAAA,SAC5F;AAEA,QAAM,MAAA,UAAA,GAAaA,8BAAiB,CAAA,OAAA,EAAS,OAAO,CAAA;AACpD,QAAO,MAAA,CAAA,IAAA;AAAA,UACL,CAAyC,sCAAA,EAAA,UAAA,CAAW,MAAM,CAAA,sBAAA,EAAyB,KAAK,IAAI,CAAA,UAAA;AAAA,SAC9F;AAEA,QAAI,IAAA,UAAA,CAAW,SAAS,CAAG,EAAA;AACzB,UAAM,MAAAC,+BAAA;AAAA,YACJC,qCAAwB,CAAA,UAAA,EAAY,IAAM,EAAA,IAAA,CAAK,IAAI,CAAA;AAAA,YACnD,EAAA;AAAA,YACA,OAAO,KAAyB,KAAA;AAC9B,cAAM,MAAA,EAAA,CAAG,YAAY,KAAK,CAAA;AAAA;AAC5B,WACF;AACA,UAAO,MAAA,CAAA,IAAA;AAAA,YACL,CAAA,2EAAA,EAA8E,KAAK,IAAI,CAAA,kBAAA;AAAA,WACzF;AAAA,SACK,MAAA;AACL,UAAO,MAAA,CAAA,IAAA;AAAA,YACL,CAAA,8DAAA,EAAiE,KAAK,IAAI,CAAA,UAAA;AAAA,WAC5E;AAAA;AACF,eACO,KAAO,EAAA;AACd,QAAO,MAAA,CAAA,KAAA;AAAA,UACL,CAAqE,kEAAA,EAAA,IAAA,CAAK,IAAI,CAAA,EAAA,EAAK,KAAK,CAAA;AAAA,SAC1F;AAAA;AACF;AACF,WACO,KAAO,EAAA;AACd,IAAO,MAAA,CAAA,KAAA;AAAA,MACL,yDAAyD,KAAK,CAAA;AAAA,KAChE;AACA,IAAM,MAAA,KAAA;AAAA;AAEV;;;;"}
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ var batchInsert = require('../utils/batchInsert.cjs.js');
4
+ var metricHelpers = require('../utils/metricHelpers.cjs.js');
5
+
6
+ async function discoverOrganizationMetrics({
7
+ api,
8
+ logger,
9
+ db,
10
+ config
11
+ }) {
12
+ if (!config.getOptionalString("copilot.organization")) {
13
+ logger.info(
14
+ "[discoverOrganizationMetrics] Skipping: Organization configuration not found."
15
+ );
16
+ return;
17
+ }
18
+ const type = "organization";
19
+ try {
20
+ const metrics = await api.fetchOrganizationCopilotUsage();
21
+ logger.info(
22
+ `[discoverOrganizationMetrics] Fetched ${metrics.length} metrics`
23
+ );
24
+ const lastDay = await db.getMostRecentDayFromMetrics(type);
25
+ logger.info(`[discoverOrganizationMetrics] Found last day: ${lastDay}`);
26
+ const newMetrics = metricHelpers.filterNewMetrics(metrics, lastDay);
27
+ logger.info(
28
+ `[discoverOrganizationMetrics] Found ${newMetrics.length} new metrics to insert`
29
+ );
30
+ if (newMetrics.length > 0) {
31
+ await batchInsert.batchInsertInChunks(
32
+ metricHelpers.prepareMetricsForInsert(newMetrics, type),
33
+ 30,
34
+ async (chunk) => {
35
+ await db.batchInsert(chunk);
36
+ }
37
+ );
38
+ logger.info(
39
+ "[discoverOrganizationMetrics] Inserted new metrics into the database"
40
+ );
41
+ } else {
42
+ logger.info(
43
+ "[discoverOrganizationMetrics] No new metrics found to insert"
44
+ );
45
+ }
46
+ } catch (error) {
47
+ logger.error(
48
+ `[discoverOrganizationMetrics] An error occurred while processing Github Copilot metrics: ${error}`
49
+ );
50
+ throw error;
51
+ }
52
+ }
53
+
54
+ exports.discoverOrganizationMetrics = discoverOrganizationMetrics;
55
+ //# sourceMappingURL=OrganizationTask.cjs.js.map
@@ -0,0 +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 { MetricsType } from '@backstage-community/plugin-copilot-common';\nimport { MetricDbRow } from '../db/DatabaseHandler';\nimport { batchInsertInChunks } from '../utils/batchInsert';\nimport {\n filterNewMetrics,\n prepareMetricsForInsert,\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 metrics = await api.fetchOrganizationCopilotUsage();\n logger.info(\n `[discoverOrganizationMetrics] Fetched ${metrics.length} metrics`,\n );\n\n const lastDay = await db.getMostRecentDayFromMetrics(type);\n logger.info(`[discoverOrganizationMetrics] Found last day: ${lastDay}`);\n\n const newMetrics = filterNewMetrics(metrics, lastDay);\n logger.info(\n `[discoverOrganizationMetrics] Found ${newMetrics.length} new metrics to insert`,\n );\n\n if (newMetrics.length > 0) {\n await batchInsertInChunks<MetricDbRow>(\n prepareMetricsForInsert(newMetrics, type),\n 30,\n async (chunk: MetricDbRow[]) => {\n await db.batchInsert(chunk);\n },\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: ${error}`,\n );\n throw error;\n }\n}\n"],"names":["filterNewMetrics","batchInsertInChunks","prepareMetricsForInsert"],"mappings":";;;;;AAwBA,eAAsB,2BAA4B,CAAA;AAAA,EAChD,GAAA;AAAA,EACA,MAAA;AAAA,EACA,EAAA;AAAA,EACA;AACF,CAA+B,EAAA;AAC7B,EAAA,IAAI,CAAC,MAAA,CAAO,iBAAkB,CAAA,sBAAsB,CAAG,EAAA;AACrD,IAAO,MAAA,CAAA,IAAA;AAAA,MACL;AAAA,KACF;AACA,IAAA;AAAA;AAGF,EAAA,MAAM,IAAoB,GAAA,cAAA;AAE1B,EAAI,IAAA;AACF,IAAM,MAAA,OAAA,GAAU,MAAM,GAAA,CAAI,6BAA8B,EAAA;AACxD,IAAO,MAAA,CAAA,IAAA;AAAA,MACL,CAAA,sCAAA,EAAyC,QAAQ,MAAM,CAAA,QAAA;AAAA,KACzD;AAEA,IAAA,MAAM,OAAU,GAAA,MAAM,EAAG,CAAA,2BAAA,CAA4B,IAAI,CAAA;AACzD,IAAO,MAAA,CAAA,IAAA,CAAK,CAAiD,8CAAA,EAAA,OAAO,CAAE,CAAA,CAAA;AAEtE,IAAM,MAAA,UAAA,GAAaA,8BAAiB,CAAA,OAAA,EAAS,OAAO,CAAA;AACpD,IAAO,MAAA,CAAA,IAAA;AAAA,MACL,CAAA,oCAAA,EAAuC,WAAW,MAAM,CAAA,sBAAA;AAAA,KAC1D;AAEA,IAAI,IAAA,UAAA,CAAW,SAAS,CAAG,EAAA;AACzB,MAAM,MAAAC,+BAAA;AAAA,QACJC,qCAAA,CAAwB,YAAY,IAAI,CAAA;AAAA,QACxC,EAAA;AAAA,QACA,OAAO,KAAyB,KAAA;AAC9B,UAAM,MAAA,EAAA,CAAG,YAAY,KAAK,CAAA;AAAA;AAC5B,OACF;AACA,MAAO,MAAA,CAAA,IAAA;AAAA,QACL;AAAA,OACF;AAAA,KACK,MAAA;AACL,MAAO,MAAA,CAAA,IAAA;AAAA,QACL;AAAA,OACF;AAAA;AACF,WACO,KAAO,EAAA;AACd,IAAO,MAAA,CAAA,KAAA;AAAA,MACL,4FAA4F,KAAK,CAAA;AAAA,KACnG;AACA,IAAM,MAAA,KAAA;AAAA;AAEV;;;;"}
@@ -0,0 +1,72 @@
1
+ 'use strict';
2
+
3
+ var batchInsert = require('../utils/batchInsert.cjs.js');
4
+ var metricHelpers = require('../utils/metricHelpers.cjs.js');
5
+
6
+ async function discoverOrganizationTeamMetrics({
7
+ api,
8
+ logger,
9
+ db,
10
+ config
11
+ }) {
12
+ if (!config.getOptionalString("copilot.organization")) {
13
+ logger.info(
14
+ "[discoverOrganizationTeamMetrics] Skipping: Organization configuration not found."
15
+ );
16
+ return;
17
+ }
18
+ const type = "organization";
19
+ try {
20
+ const teams = await api.fetchOrganizationTeams();
21
+ logger.info(
22
+ `[discoverOrganizationTeamMetrics] Fetched ${teams.length} teams`
23
+ );
24
+ for (const team of teams) {
25
+ try {
26
+ logger.info(
27
+ `[discoverOrganizationTeamMetrics] Fetching metrics for team: ${team.slug}`
28
+ );
29
+ const metrics = await api.fetchOrganizationTeamCopilotUsage(team.slug);
30
+ logger.info(
31
+ `[discoverOrganizationTeamMetrics] Fetched ${metrics.length} metrics for team: ${team.slug}`
32
+ );
33
+ const lastDay = await db.getMostRecentDayFromMetrics(type, team.slug);
34
+ logger.info(
35
+ `[discoverOrganizationTeamMetrics] Found last processed day for team ${team.slug}: ${lastDay}`
36
+ );
37
+ const newMetrics = metricHelpers.filterNewMetrics(metrics, lastDay);
38
+ logger.info(
39
+ `[discoverOrganizationTeamMetrics] Found ${newMetrics.length} new metrics for team ${team.slug} to insert`
40
+ );
41
+ if (newMetrics.length > 0) {
42
+ await batchInsert.batchInsertInChunks(
43
+ metricHelpers.prepareMetricsForInsert(newMetrics, type, team.slug),
44
+ 30,
45
+ async (chunk) => {
46
+ await db.batchInsert(chunk);
47
+ }
48
+ );
49
+ logger.info(
50
+ `[discoverOrganizationTeamMetrics] Successfully inserted new metrics for team ${team.slug} into the database`
51
+ );
52
+ } else {
53
+ logger.info(
54
+ `[discoverOrganizationTeamMetrics] No new metrics found for team ${team.slug} to insert`
55
+ );
56
+ }
57
+ } catch (error) {
58
+ logger.error(
59
+ `[discoverOrganizationTeamMetrics] Error processing metrics for team ${team.slug}: ${error}`
60
+ );
61
+ }
62
+ }
63
+ } catch (error) {
64
+ logger.error(
65
+ `[discoverOrganizationTeamMetrics] Error fetching teams: ${error}`
66
+ );
67
+ throw error;
68
+ }
69
+ }
70
+
71
+ exports.discoverOrganizationTeamMetrics = discoverOrganizationTeamMetrics;
72
+ //# sourceMappingURL=OrganizationTeamTask.cjs.js.map
@@ -0,0 +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 { MetricsType } from '@backstage-community/plugin-copilot-common';\nimport { MetricDbRow } from '../db/DatabaseHandler';\nimport { batchInsertInChunks } from '../utils/batchInsert';\nimport {\n filterNewMetrics,\n prepareMetricsForInsert,\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 for (const team of teams) {\n try {\n logger.info(\n `[discoverOrganizationTeamMetrics] Fetching metrics for team: ${team.slug}`,\n );\n\n const metrics = await api.fetchOrganizationTeamCopilotUsage(team.slug);\n logger.info(\n `[discoverOrganizationTeamMetrics] Fetched ${metrics.length} metrics for team: ${team.slug}`,\n );\n\n const lastDay = await db.getMostRecentDayFromMetrics(type, team.slug);\n logger.info(\n `[discoverOrganizationTeamMetrics] Found last processed day for team ${team.slug}: ${lastDay}`,\n );\n\n const newMetrics = filterNewMetrics(metrics, lastDay);\n logger.info(\n `[discoverOrganizationTeamMetrics] Found ${newMetrics.length} new metrics for team ${team.slug} to insert`,\n );\n\n if (newMetrics.length > 0) {\n await batchInsertInChunks<MetricDbRow>(\n prepareMetricsForInsert(newMetrics, type, team.slug),\n 30,\n async (chunk: MetricDbRow[]) => {\n await db.batchInsert(chunk);\n },\n );\n logger.info(\n `[discoverOrganizationTeamMetrics] Successfully inserted new metrics for team ${team.slug} into the database`,\n );\n } else {\n logger.info(\n `[discoverOrganizationTeamMetrics] No new metrics found for team ${team.slug} to insert`,\n );\n }\n } catch (error) {\n logger.error(\n `[discoverOrganizationTeamMetrics] Error processing metrics for team ${team.slug}: ${error}`,\n );\n }\n }\n } catch (error) {\n logger.error(\n `[discoverOrganizationTeamMetrics] Error fetching teams: ${error}`,\n );\n throw error;\n }\n}\n"],"names":["filterNewMetrics","batchInsertInChunks","prepareMetricsForInsert"],"mappings":";;;;;AAyBA,eAAsB,+BAAgC,CAAA;AAAA,EACpD,GAAA;AAAA,EACA,MAAA;AAAA,EACA,EAAA;AAAA,EACA;AACF,CAA+B,EAAA;AAC7B,EAAA,IAAI,CAAC,MAAA,CAAO,iBAAkB,CAAA,sBAAsB,CAAG,EAAA;AACrD,IAAO,MAAA,CAAA,IAAA;AAAA,MACL;AAAA,KACF;AACA,IAAA;AAAA;AAGF,EAAA,MAAM,IAAoB,GAAA,cAAA;AAE1B,EAAI,IAAA;AACF,IAAM,MAAA,KAAA,GAAQ,MAAM,GAAA,CAAI,sBAAuB,EAAA;AAC/C,IAAO,MAAA,CAAA,IAAA;AAAA,MACL,CAAA,0CAAA,EAA6C,MAAM,MAAM,CAAA,MAAA;AAAA,KAC3D;AAEA,IAAA,KAAA,MAAW,QAAQ,KAAO,EAAA;AACxB,MAAI,IAAA;AACF,QAAO,MAAA,CAAA,IAAA;AAAA,UACL,CAAA,6DAAA,EAAgE,KAAK,IAAI,CAAA;AAAA,SAC3E;AAEA,QAAA,MAAM,OAAU,GAAA,MAAM,GAAI,CAAA,iCAAA,CAAkC,KAAK,IAAI,CAAA;AACrE,QAAO,MAAA,CAAA,IAAA;AAAA,UACL,CAA6C,0CAAA,EAAA,OAAA,CAAQ,MAAM,CAAA,mBAAA,EAAsB,KAAK,IAAI,CAAA;AAAA,SAC5F;AAEA,QAAA,MAAM,UAAU,MAAM,EAAA,CAAG,2BAA4B,CAAA,IAAA,EAAM,KAAK,IAAI,CAAA;AACpE,QAAO,MAAA,CAAA,IAAA;AAAA,UACL,CAAuE,oEAAA,EAAA,IAAA,CAAK,IAAI,CAAA,EAAA,EAAK,OAAO,CAAA;AAAA,SAC9F;AAEA,QAAM,MAAA,UAAA,GAAaA,8BAAiB,CAAA,OAAA,EAAS,OAAO,CAAA;AACpD,QAAO,MAAA,CAAA,IAAA;AAAA,UACL,CAA2C,wCAAA,EAAA,UAAA,CAAW,MAAM,CAAA,sBAAA,EAAyB,KAAK,IAAI,CAAA,UAAA;AAAA,SAChG;AAEA,QAAI,IAAA,UAAA,CAAW,SAAS,CAAG,EAAA;AACzB,UAAM,MAAAC,+BAAA;AAAA,YACJC,qCAAwB,CAAA,UAAA,EAAY,IAAM,EAAA,IAAA,CAAK,IAAI,CAAA;AAAA,YACnD,EAAA;AAAA,YACA,OAAO,KAAyB,KAAA;AAC9B,cAAM,MAAA,EAAA,CAAG,YAAY,KAAK,CAAA;AAAA;AAC5B,WACF;AACA,UAAO,MAAA,CAAA,IAAA;AAAA,YACL,CAAA,6EAAA,EAAgF,KAAK,IAAI,CAAA,kBAAA;AAAA,WAC3F;AAAA,SACK,MAAA;AACL,UAAO,MAAA,CAAA,IAAA;AAAA,YACL,CAAA,gEAAA,EAAmE,KAAK,IAAI,CAAA,UAAA;AAAA,WAC9E;AAAA;AACF,eACO,KAAO,EAAA;AACd,QAAO,MAAA,CAAA,KAAA;AAAA,UACL,CAAuE,oEAAA,EAAA,IAAA,CAAK,IAAI,CAAA,EAAA,EAAK,KAAK,CAAA;AAAA,SAC5F;AAAA;AACF;AACF,WACO,KAAO,EAAA;AACd,IAAO,MAAA,CAAA,KAAA;AAAA,MACL,2DAA2D,KAAK,CAAA;AAAA,KAClE;AACA,IAAM,MAAA,KAAA;AAAA;AAEV;;;;"}
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var OrganizationTask = require('./OrganizationTask.cjs.js');
6
+ var OrganizationTeamTask = require('./OrganizationTeamTask.cjs.js');
7
+ var EnterpriseTask = require('./EnterpriseTask.cjs.js');
8
+ var EnterpriseTeamTask = require('./EnterpriseTeamTask.cjs.js');
9
+
10
+ class TaskManagement {
11
+ constructor(options) {
12
+ this.options = options;
13
+ this.tasks = [
14
+ () => OrganizationTask.discoverOrganizationMetrics(this.options),
15
+ () => OrganizationTeamTask.discoverOrganizationTeamMetrics(this.options),
16
+ () => EnterpriseTask.discoverEnterpriseMetrics(this.options),
17
+ () => EnterpriseTeamTask.discoverEnterpriseTeamMetrics(this.options)
18
+ ];
19
+ }
20
+ tasks;
21
+ static create(options) {
22
+ return new TaskManagement(options);
23
+ }
24
+ async runAsync() {
25
+ this.options.logger.info(
26
+ `[TaskManagement] Starting processing of ${this.tasks.length} tasks`
27
+ );
28
+ const taskPromises = this.tasks.map(async (task) => {
29
+ try {
30
+ await task();
31
+ } catch (e) {
32
+ this.options.logger.warn(
33
+ `[TaskManagement] Failed to process task: ${e.message}`
34
+ );
35
+ }
36
+ });
37
+ await Promise.all(taskPromises);
38
+ this.options.logger.info(
39
+ `[TaskManagement] Completed processing of all tasks`
40
+ );
41
+ }
42
+ }
43
+
44
+ exports.default = TaskManagement;
45
+ //# sourceMappingURL=TaskManagement.cjs.js.map
@@ -0,0 +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 this.options.logger.warn(\n `[TaskManagement] Failed to process task: ${e.message}`,\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,IAAA,CAAK,QAAQ,MAAO,CAAA,IAAA;AAAA,UAClB,CAAA,yCAAA,EAA4C,EAAE,OAAO,CAAA;AAAA,SACvD;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;;;;"}
@@ -5,13 +5,11 @@ var integration = require('@backstage/integration');
5
5
  const getGithubInfo = async (config) => {
6
6
  const integrations = integration.ScmIntegrations.fromConfig(config);
7
7
  const host = config.getString("copilot.host");
8
- const enterprise = config.getString("copilot.enterprise");
8
+ const enterprise = config.getOptionalString("copilot.enterprise");
9
+ const organization = config.getOptionalString("copilot.organization");
9
10
  if (!host) {
10
11
  throw new Error("The host configuration is missing from the config.");
11
12
  }
12
- if (!enterprise) {
13
- throw new Error("The enterprise configuration is missing from the config.");
14
- }
15
13
  const githubConfig = integrations.github.byHost(host)?.config;
16
14
  if (!githubConfig) {
17
15
  throw new Error(
@@ -27,7 +25,8 @@ const getGithubInfo = async (config) => {
27
25
  return {
28
26
  apiBaseUrl,
29
27
  credentials,
30
- enterprise
28
+ enterprise,
29
+ organization
31
30
  };
32
31
  };
33
32
 
@@ -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 { GithubCredentials, ScmIntegrations } from '@backstage/integration';\n\nexport type GithubInfo = {\n credentials: GithubCredentials;\n apiBaseUrl: string;\n enterprise: string;\n};\n\nexport const getGithubInfo = async (config: Config): Promise<GithubInfo> => {\n const integrations = ScmIntegrations.fromConfig(config);\n\n const host = config.getString('copilot.host');\n const enterprise = config.getString('copilot.enterprise');\n\n if (!host) {\n throw new Error('The host configuration is missing from the config.');\n }\n\n if (!enterprise) {\n throw new Error('The enterprise configuration is missing from the config.');\n }\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 apiBaseUrl = githubConfig.apiBaseUrl ?? 'https://api.github.com';\n\n const credentials: GithubCredentials = {\n type: 'token',\n headers: { Authorization: `Bearer ${githubConfig.token}` },\n token: githubConfig.token,\n };\n\n return {\n apiBaseUrl,\n credentials,\n enterprise,\n };\n};\n"],"names":["ScmIntegrations"],"mappings":";;;;AAyBa,MAAA,aAAA,GAAgB,OAAO,MAAwC,KAAA;AAC1E,EAAM,MAAA,YAAA,GAAeA,2BAAgB,CAAA,UAAA,CAAW,MAAM,CAAA,CAAA;AAEtD,EAAM,MAAA,IAAA,GAAO,MAAO,CAAA,SAAA,CAAU,cAAc,CAAA,CAAA;AAC5C,EAAM,MAAA,UAAA,GAAa,MAAO,CAAA,SAAA,CAAU,oBAAoB,CAAA,CAAA;AAExD,EAAA,IAAI,CAAC,IAAM,EAAA;AACT,IAAM,MAAA,IAAI,MAAM,oDAAoD,CAAA,CAAA;AAAA,GACtE;AAEA,EAAA,IAAI,CAAC,UAAY,EAAA;AACf,IAAM,MAAA,IAAI,MAAM,0DAA0D,CAAA,CAAA;AAAA,GAC5E;AAEA,EAAA,MAAM,YAAe,GAAA,YAAA,CAAa,MAAO,CAAA,MAAA,CAAO,IAAI,CAAG,EAAA,MAAA,CAAA;AAEvD,EAAA,IAAI,CAAC,YAAc,EAAA;AACjB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,kCAAkC,IAAI,CAAA,2BAAA,CAAA;AAAA,KACxC,CAAA;AAAA,GACF;AAEA,EAAM,MAAA,UAAA,GAAa,aAAa,UAAc,IAAA,wBAAA,CAAA;AAE9C,EAAA,MAAM,WAAiC,GAAA;AAAA,IACrC,IAAM,EAAA,OAAA;AAAA,IACN,SAAS,EAAE,aAAA,EAAe,CAAU,OAAA,EAAA,YAAA,CAAa,KAAK,CAAG,CAAA,EAAA;AAAA,IACzD,OAAO,YAAa,CAAA,KAAA;AAAA,GACtB,CAAA;AAEA,EAAO,OAAA;AAAA,IACL,UAAA;AAAA,IACA,WAAA;AAAA,IACA,UAAA;AAAA,GACF,CAAA;AACF;;;;"}
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 { GithubCredentials, ScmIntegrations } from '@backstage/integration';\n\nexport type GithubInfo = {\n credentials: GithubCredentials;\n apiBaseUrl: string;\n enterprise?: string;\n organization?: string;\n};\n\nexport const getGithubInfo = async (config: Config): Promise<GithubInfo> => {\n const integrations = ScmIntegrations.fromConfig(config);\n\n const host = config.getString('copilot.host');\n const enterprise = config.getOptionalString('copilot.enterprise');\n const organization = config.getOptionalString('copilot.organization');\n\n if (!host) {\n throw new Error('The host configuration is missing from the config.');\n }\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 apiBaseUrl = githubConfig.apiBaseUrl ?? 'https://api.github.com';\n\n const credentials: GithubCredentials = {\n type: 'token',\n headers: { Authorization: `Bearer ${githubConfig.token}` },\n token: githubConfig.token,\n };\n\n return {\n apiBaseUrl,\n credentials,\n enterprise,\n organization,\n };\n};\n"],"names":["ScmIntegrations"],"mappings":";;;;AA0Ba,MAAA,aAAA,GAAgB,OAAO,MAAwC,KAAA;AAC1E,EAAM,MAAA,YAAA,GAAeA,2BAAgB,CAAA,UAAA,CAAW,MAAM,CAAA;AAEtD,EAAM,MAAA,IAAA,GAAO,MAAO,CAAA,SAAA,CAAU,cAAc,CAAA;AAC5C,EAAM,MAAA,UAAA,GAAa,MAAO,CAAA,iBAAA,CAAkB,oBAAoB,CAAA;AAChE,EAAM,MAAA,YAAA,GAAe,MAAO,CAAA,iBAAA,CAAkB,sBAAsB,CAAA;AAEpE,EAAA,IAAI,CAAC,IAAM,EAAA;AACT,IAAM,MAAA,IAAI,MAAM,oDAAoD,CAAA;AAAA;AAGtE,EAAA,MAAM,YAAe,GAAA,YAAA,CAAa,MAAO,CAAA,MAAA,CAAO,IAAI,CAAG,EAAA,MAAA;AAEvD,EAAA,IAAI,CAAC,YAAc,EAAA;AACjB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,kCAAkC,IAAI,CAAA,2BAAA;AAAA,KACxC;AAAA;AAGF,EAAM,MAAA,UAAA,GAAa,aAAa,UAAc,IAAA,wBAAA;AAE9C,EAAA,MAAM,WAAiC,GAAA;AAAA,IACrC,IAAM,EAAA,OAAA;AAAA,IACN,SAAS,EAAE,aAAA,EAAe,CAAU,OAAA,EAAA,YAAA,CAAa,KAAK,CAAG,CAAA,EAAA;AAAA,IACzD,OAAO,YAAa,CAAA;AAAA,GACtB;AAEA,EAAO,OAAA;AAAA,IACL,UAAA;AAAA,IACA,WAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,GACF;AACF;;;;"}
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ async function batchInsertInChunks(data, chunkSize, batchInsertFunc) {
4
+ for (let i = 0; i < data.length; i += chunkSize) {
5
+ const chunk = data.slice(i, i + chunkSize);
6
+ await batchInsertFunc(chunk);
7
+ }
8
+ }
9
+
10
+ exports.batchInsertInChunks = batchInsertInChunks;
11
+ //# sourceMappingURL=batchInsert.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batchInsert.cjs.js","sources":["../../src/utils/batchInsert.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 */\nexport async function batchInsertInChunks<T>(\n data: T[],\n chunkSize: number,\n batchInsertFunc: (chunk: T[]) => Promise<void>,\n): Promise<void> {\n for (let i = 0; i < data.length; i += chunkSize) {\n const chunk = data.slice(i, i + chunkSize);\n await batchInsertFunc(chunk);\n }\n}\n"],"names":[],"mappings":";;AAesB,eAAA,mBAAA,CACpB,IACA,EAAA,SAAA,EACA,eACe,EAAA;AACf,EAAA,KAAA,IAAS,IAAI,CAAG,EAAA,CAAA,GAAI,IAAK,CAAA,MAAA,EAAQ,KAAK,SAAW,EAAA;AAC/C,IAAA,MAAM,KAAQ,GAAA,IAAA,CAAK,KAAM,CAAA,CAAA,EAAG,IAAI,SAAS,CAAA;AACzC,IAAA,MAAM,gBAAgB,KAAK,CAAA;AAAA;AAE/B;;;;"}
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ var luxon = require('luxon');
4
+
5
+ function filterNewMetrics(metrics, lastDay) {
6
+ return metrics.sort(
7
+ (a, b) => luxon.DateTime.fromISO(a.day).toMillis() - luxon.DateTime.fromISO(b.day).toMillis()
8
+ ).filter((metric) => {
9
+ const metricDate = luxon.DateTime.fromISO(metric.day);
10
+ const lastDayDate = lastDay ? luxon.DateTime.fromJSDate(new Date(lastDay)) : null;
11
+ return !lastDay || lastDayDate?.isValid && metricDate > lastDayDate;
12
+ });
13
+ }
14
+ function prepareMetricsForInsert(metrics, type, team_name) {
15
+ return metrics.map(({ breakdown, ...rest }) => ({
16
+ ...rest,
17
+ type,
18
+ team_name,
19
+ breakdown: JSON.stringify(breakdown)
20
+ }));
21
+ }
22
+
23
+ exports.filterNewMetrics = filterNewMetrics;
24
+ exports.prepareMetricsForInsert = prepareMetricsForInsert;
25
+ //# sourceMappingURL=metricHelpers.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metricHelpers.cjs.js","sources":["../../src/utils/metricHelpers.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 { DateTime } from 'luxon';\nimport { MetricDbRow } from '../db/DatabaseHandler';\nimport {\n Metric,\n MetricsType,\n} from '@backstage-community/plugin-copilot-common';\n\nexport function filterNewMetrics(\n metrics: Metric[],\n lastDay?: string,\n): Metric[] {\n return metrics\n .sort(\n (a, b) =>\n DateTime.fromISO(a.day).toMillis() - DateTime.fromISO(b.day).toMillis(),\n )\n .filter(metric => {\n const metricDate = DateTime.fromISO(metric.day);\n\n const lastDayDate = lastDay\n ? DateTime.fromJSDate(new Date(lastDay))\n : null;\n\n return !lastDay || (lastDayDate?.isValid && metricDate > lastDayDate);\n });\n}\n\nexport function prepareMetricsForInsert(\n metrics: Metric[],\n type: MetricsType,\n team_name?: string,\n): MetricDbRow[] {\n return metrics.map(({ breakdown, ...rest }) => ({\n ...rest,\n type,\n team_name,\n breakdown: JSON.stringify(breakdown),\n })) as MetricDbRow[];\n}\n"],"names":["DateTime"],"mappings":";;;;AAsBgB,SAAA,gBAAA,CACd,SACA,OACU,EAAA;AACV,EAAA,OAAO,OACJ,CAAA,IAAA;AAAA,IACC,CAAC,CAAA,EAAG,CACF,KAAAA,cAAA,CAAS,QAAQ,CAAE,CAAA,GAAG,CAAE,CAAA,QAAA,KAAaA,cAAS,CAAA,OAAA,CAAQ,CAAE,CAAA,GAAG,EAAE,QAAS;AAAA,GAC1E,CACC,OAAO,CAAU,MAAA,KAAA;AAChB,IAAA,MAAM,UAAa,GAAAA,cAAA,CAAS,OAAQ,CAAA,MAAA,CAAO,GAAG,CAAA;AAE9C,IAAM,MAAA,WAAA,GAAc,UAChBA,cAAS,CAAA,UAAA,CAAW,IAAI,IAAK,CAAA,OAAO,CAAC,CACrC,GAAA,IAAA;AAEJ,IAAA,OAAO,CAAC,OAAA,IAAY,WAAa,EAAA,OAAA,IAAW,UAAa,GAAA,WAAA;AAAA,GAC1D,CAAA;AACL;AAEgB,SAAA,uBAAA,CACd,OACA,EAAA,IAAA,EACA,SACe,EAAA;AACf,EAAA,OAAO,QAAQ,GAAI,CAAA,CAAC,EAAE,SAAW,EAAA,GAAG,MAAY,MAAA;AAAA,IAC9C,GAAG,IAAA;AAAA,IACH,IAAA;AAAA,IACA,SAAA;AAAA,IACA,SAAA,EAAW,IAAK,CAAA,SAAA,CAAU,SAAS;AAAA,GACnC,CAAA,CAAA;AACJ;;;;;"}
@@ -0,0 +1,48 @@
1
+ /*
2
+ * Copyright 2024 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ /**
17
+ * @param {import('knex').Knex} knex
18
+ */
19
+ exports.up = async function up(knex) {
20
+ await knex.schema.table('metrics', table => {
21
+ table
22
+ .string('type', 50)
23
+ .defaultTo('enterprise')
24
+ .notNullable()
25
+ .comment('Type of the metrics data: enterprise, organization');
26
+
27
+ table.string('team_name', 255).nullable().comment('Name of the team');
28
+
29
+ table.dropPrimary();
30
+
31
+ table.unique(['day', 'type', 'team_name'], 'uk_day_type_team_name');
32
+ });
33
+ };
34
+
35
+ /**
36
+ * @param {import('knex').Knex} knex
37
+ */
38
+ exports.down = async function down(knex) {
39
+ await knex.schema.table('metrics', table => {
40
+ table.dropUnique(['day', 'type', 'team_name']);
41
+
42
+ table.dropColumn('type');
43
+ table.dropColumn('team_name');
44
+
45
+ table.primary('day');
46
+ table.index('day', 'idx_metrics_day');
47
+ });
48
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage-community/plugin-copilot-backend",
3
- "version": "0.1.6",
3
+ "version": "0.3.0",
4
4
  "homepage": "https://backstage.io",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.cjs.js",
@@ -39,14 +39,14 @@
39
39
  "postpack": "backstage-cli package postpack"
40
40
  },
41
41
  "dependencies": {
42
- "@backstage-community/plugin-copilot-common": "^0.2.2",
43
- "@backstage/backend-app-api": "^1.0.1",
44
- "@backstage/backend-defaults": "^0.5.2",
45
- "@backstage/backend-plugin-api": "^1.0.1",
42
+ "@backstage-community/plugin-copilot-common": "^0.4.0",
43
+ "@backstage/backend-app-api": "^1.1.0",
44
+ "@backstage/backend-defaults": "^0.6.2",
45
+ "@backstage/backend-plugin-api": "^1.1.0",
46
46
  "@backstage/backend-tasks": "^0.6.1",
47
- "@backstage/config": "^1.2.0",
48
- "@backstage/errors": "^1.2.4",
49
- "@backstage/integration": "^1.15.1",
47
+ "@backstage/config": "^1.3.1",
48
+ "@backstage/errors": "^1.2.6",
49
+ "@backstage/integration": "^1.16.0",
50
50
  "@types/express": "*",
51
51
  "express": "^4.17.1",
52
52
  "express-promise-router": "^4.1.0",
@@ -54,18 +54,26 @@
54
54
  "luxon": "^3.5.0",
55
55
  "node-fetch": "^2.6.7",
56
56
  "winston": "^3.2.1",
57
- "yn": "^4.0.0"
57
+ "yn": "^4.0.0",
58
+ "zod": "^3.23.8"
58
59
  },
59
60
  "devDependencies": {
60
- "@backstage/backend-test-utils": "^1.0.2",
61
- "@backstage/cli": "^0.28.0",
62
- "@backstage/plugin-auth-backend": "^0.23.1",
63
- "@backstage/plugin-auth-backend-module-guest-provider": "^0.2.1",
64
- "@backstage/test-utils": "^1.7.0",
61
+ "@backstage/backend-test-utils": "^1.2.0",
62
+ "@backstage/cli": "^0.29.4",
63
+ "@backstage/plugin-auth-backend": "^0.24.1",
64
+ "@backstage/plugin-auth-backend-module-guest-provider": "^0.2.3",
65
+ "@backstage/test-utils": "^1.7.3",
65
66
  "@types/node-fetch": "^2.6.11",
66
67
  "@types/supertest": "^2.0.8",
67
68
  "msw": "^1.0.0",
68
69
  "supertest": "^6.2.4"
69
70
  },
70
- "configSchema": "config.d.ts"
71
+ "configSchema": "config.d.ts",
72
+ "typesVersions": {
73
+ "*": {
74
+ "index": [
75
+ "dist/index.d.ts"
76
+ ]
77
+ }
78
+ }
71
79
  }
@@ -1,59 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var luxon = require('luxon');
6
-
7
- class Scheduler {
8
- constructor(options) {
9
- this.options = options;
10
- }
11
- static create(options) {
12
- return new Scheduler(options);
13
- }
14
- async run() {
15
- try {
16
- this.options.logger.info("Starting Github Copilot Processor");
17
- const copilotMetrics = await this.options.api.getCopilotUsageDataForEnterprise();
18
- this.options.logger.info(`Fetched ${copilotMetrics.length} metrics`);
19
- const lastDay = await this.options.db.getMostRecentDayFromMetrics();
20
- this.options.logger.info(`Found last day: ${lastDay}`);
21
- const diff = copilotMetrics.sort(
22
- (a, b) => luxon.DateTime.fromISO(a.day).toMillis() - luxon.DateTime.fromISO(b.day).toMillis()
23
- ).filter((metric) => {
24
- const metricDate = luxon.DateTime.fromISO(metric.day);
25
- const lastDayDate = lastDay ? luxon.DateTime.fromISO(lastDay) : null;
26
- return !lastDayDate || metricDate > lastDayDate;
27
- }).map(({ breakdown, ...rest }) => ({
28
- ...rest,
29
- breakdown: JSON.stringify(breakdown)
30
- }));
31
- this.options.logger.info(`Found ${diff.length} new metrics to insert`);
32
- if (diff.length > 0) {
33
- await batchInsertInChunks(
34
- diff,
35
- 30,
36
- async (chunk) => {
37
- await this.options.db.batchInsert(chunk);
38
- }
39
- );
40
- this.options.logger.info("Inserted new metrics into the database");
41
- } else {
42
- this.options.logger.info("No new metrics found to insert");
43
- }
44
- } catch (error) {
45
- this.options.logger.error(
46
- `An error occurred while processing Github Copilot metrics: ${error}`
47
- );
48
- }
49
- }
50
- }
51
- async function batchInsertInChunks(data, chunkSize, batchInsertFunc) {
52
- for (let i = 0; i < data.length; i += chunkSize) {
53
- const chunk = data.slice(i, i + chunkSize);
54
- await batchInsertFunc(chunk);
55
- }
56
- }
57
-
58
- exports.default = Scheduler;
59
- //# sourceMappingURL=Scheduler.cjs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Scheduler.cjs.js","sources":["../../src/task/Scheduler.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 { LoggerService } from '@backstage/backend-plugin-api';\nimport { GithubClient } from '../client/GithubClient';\nimport { DatabaseHandler, MetricDbRow } from '../db/DatabaseHandler';\nimport { Config } from '@backstage/config';\nimport { DateTime } from 'luxon';\n\ntype Options = {\n api: GithubClient;\n config: Config;\n logger: LoggerService;\n db: DatabaseHandler;\n};\n\nexport default class Scheduler {\n constructor(private readonly options: Options) {}\n\n static create(options: Options) {\n return new Scheduler(options);\n }\n\n async run() {\n try {\n this.options.logger.info('Starting Github Copilot Processor');\n\n const copilotMetrics =\n await this.options.api.getCopilotUsageDataForEnterprise();\n this.options.logger.info(`Fetched ${copilotMetrics.length} metrics`);\n\n const lastDay = await this.options.db.getMostRecentDayFromMetrics();\n this.options.logger.info(`Found last day: ${lastDay}`);\n\n const diff = copilotMetrics\n .sort(\n (a, b) =>\n DateTime.fromISO(a.day).toMillis() -\n DateTime.fromISO(b.day).toMillis(),\n )\n .filter(metric => {\n const metricDate = DateTime.fromISO(metric.day);\n const lastDayDate = lastDay ? DateTime.fromISO(lastDay) : null;\n return !lastDayDate || metricDate > lastDayDate;\n })\n .map(({ breakdown, ...rest }) => ({\n ...rest,\n breakdown: JSON.stringify(breakdown),\n }));\n\n this.options.logger.info(`Found ${diff.length} new metrics to insert`);\n\n if (diff.length > 0) {\n await batchInsertInChunks<MetricDbRow>(\n diff,\n 30,\n async (chunk: MetricDbRow[]) => {\n await this.options.db.batchInsert(chunk);\n },\n );\n this.options.logger.info('Inserted new metrics into the database');\n } else {\n this.options.logger.info('No new metrics found to insert');\n }\n } catch (error) {\n this.options.logger.error(\n `An error occurred while processing Github Copilot metrics: ${error}`,\n );\n }\n }\n}\n\nasync function batchInsertInChunks<T>(\n data: T[],\n chunkSize: number,\n batchInsertFunc: (chunk: T[]) => Promise<void>,\n): Promise<void> {\n for (let i = 0; i < data.length; i += chunkSize) {\n const chunk = data.slice(i, i + chunkSize);\n await batchInsertFunc(chunk);\n }\n}\n"],"names":["DateTime"],"mappings":"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"}