@backstage-community/plugin-copilot-backend 0.1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # @backstage-community/plugin-copilot-backend
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2d5f011: Introduced the GitHub Copilot plugin, checkout the plugin's [`README.md`](https://github.com/backstage/community-plugins/tree/main/workspaces/copilot/plugins/copilot) for more details!
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [2d5f011]
12
+ - @backstage-community/plugin-copilot-common@0.1.0
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # GitHub Copilot Plugin
2
+
3
+ This GitHub Copilot plugin integrates with Backstage to provide metrics and insights for members of your organization or enterprise.
4
+
5
+ ## Installation
6
+
7
+ ### New Backend System
8
+
9
+ To configure the plugin using the new backend system:
10
+
11
+ 1. In the `packages/backend/src/index.ts` file, add the following:
12
+
13
+ ```typescript
14
+ import { createBackend } from '@backstage/backend-defaults';
15
+
16
+ const backend = createBackend();
17
+
18
+ backend.add(import('@backstage-community/plugin-copilot'));
19
+
20
+ backend.start();
21
+ ```
22
+
23
+ ### Old System
24
+
25
+ To install the plugin using the old method:
26
+
27
+ 1. Add the `@backstage-community/plugin-copilot` package to your backend:
28
+
29
+ ```sh
30
+ yarn --cwd packages/backend add @backstage-community/plugin-copilot
31
+ ```
32
+
33
+ 2. In your `packages/backend/src/plugins/copilot.ts` file, add the following code:
34
+
35
+ ```typescript
36
+ import { TaskScheduleDefinition } from '@backstage/backend-tasks';
37
+ import { createRouterFromConfig } from '@backstage-community/plugin-copilot';
38
+
39
+ export default async function createPlugin(): Promise<void> {
40
+ const schedule: TaskScheduleDefinition = {
41
+ frequency: { cron: '0 2 * * *' },
42
+ timeout: { minutes: 15 },
43
+ initialDelay: { seconds: 15 },
44
+ };
45
+
46
+ return createRouterFromConfig({ schedule });
47
+ }
48
+ ```
49
+
50
+ 3. Integrate the plugin into the main backend router in `packages/backend/src/index.ts`:
51
+
52
+ ```typescript
53
+ import { createRouterFromConfig } from './plugins/copilot';
54
+
55
+ async function main() {
56
+ // Backend setup
57
+ const env = createEnv('copilot');
58
+ // Plugin registration
59
+ apiRouter.use('/copilot', await createRouterFromConfig(env));
60
+ }
61
+ ```
62
+
63
+ ## Configuration
64
+
65
+ ### Environment Variables
66
+
67
+ To configure the GitHub Copilot plugin, you need to set the following environment variables:
68
+
69
+ - **`copilot.host`**: The host URL for your GitHub Copilot instance (e.g., `github.com` or `github.enterprise.com`).
70
+ - **`copilot.enterprise`**: The name of your GitHub Enterprise instance (e.g., `my-enterprise`).
71
+
72
+ These variables are used to configure the plugin and ensure it communicates with the correct GitHub instance.
73
+
74
+ ### GitHub Credentials
75
+
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`.
77
+
78
+ Ensure that your GitHub integration in the Backstage configuration includes the necessary token for the `GithubCredentialsProvider` to work correctly.
79
+
80
+ ### YAML Configuration Example
81
+
82
+ ```yaml
83
+ copilot:
84
+ scheduler:
85
+ frequency:
86
+ hours: 2
87
+ timeout:
88
+ minutes: 2
89
+ initialDelay:
90
+ seconds: 15
91
+ host: YOUR_GITHUB_HOST_HERE
92
+ enterprise: YOUR_ENTERPRISE_NAME_HERE
93
+ ```
94
+
95
+ ### Generating GitHub Copilot Token
96
+
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.
101
+
102
+ ### API Documentation
103
+
104
+ For more details on using the GitHub Copilot API:
105
+
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.
107
+
108
+ ## Run
109
+
110
+ 1. Start the backend:
111
+
112
+ ```sh
113
+ yarn start-backend
114
+ ```
115
+
116
+ 2. Verify the plugin is running by accessing `http://localhost:7007/api/copilot/health`.
117
+
118
+ ## Links
119
+
120
+ - [GitHub Copilot Plugin Frontend](https://github.com/backstage/backstage/tree/master/plugins/copilot)
121
+ - [Backstage Homepage](https://backstage.io)
package/config.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ /*
2
+ * Copyright 2023 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
+ import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api';
18
+
19
+ /**
20
+ * Configuration interface for the GitHub Copilot Plugin.
21
+ */
22
+ export interface Config {
23
+ /** Configuration options for the GitHub Copilot Plugin */
24
+ copilot?: {
25
+ /**
26
+ * Schedule definition for the GitHub Copilot Plugin tasks.
27
+ */
28
+ schedule?: SchedulerServiceTaskScheduleDefinition;
29
+ /**
30
+ * The name of the GitHub enterprise.
31
+ */
32
+ enterprise?: string;
33
+ /**
34
+ * The host for GitHub Copilot integration.
35
+ */
36
+ host?: string;
37
+ };
38
+ }
@@ -0,0 +1,266 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var express = require('express');
6
+ var Router = require('express-promise-router');
7
+ var rootHttpRouter = require('@backstage/backend-defaults/rootHttpRouter');
8
+ var backendPluginApi = require('@backstage/backend-plugin-api');
9
+ var luxon = require('luxon');
10
+ var errors = require('@backstage/errors');
11
+ var fetch = require('node-fetch');
12
+ var integration = require('@backstage/integration');
13
+
14
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
15
+
16
+ var express__default = /*#__PURE__*/_interopDefaultCompat(express);
17
+ var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
18
+ var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
19
+
20
+ const migrationsDir = backendPluginApi.resolvePackagePath(
21
+ "@backstage-community/plugin-copilot-backend",
22
+ "migrations"
23
+ );
24
+ class DatabaseHandler {
25
+ constructor(db) {
26
+ this.db = db;
27
+ }
28
+ static async create(options) {
29
+ const { database } = options;
30
+ const client = await database.getClient();
31
+ if (!database.migrations?.skip) {
32
+ await client.migrate.latest({
33
+ directory: migrationsDir
34
+ });
35
+ }
36
+ return new DatabaseHandler(client);
37
+ }
38
+ async getByPeriod(startDate, endDate) {
39
+ const records = await this.db("metrics").whereBetween("day", [
40
+ startDate,
41
+ endDate
42
+ ]);
43
+ return records ?? [];
44
+ }
45
+ async getPeriodRange() {
46
+ const minDate = await this.db("metrics").orderBy("day", "asc").first("day");
47
+ const maxDate = await this.db("metrics").orderBy("day", "desc").first("day");
48
+ if (!minDate?.day || !maxDate?.day) return void 0;
49
+ return { minDate: minDate.day, maxDate: maxDate.day };
50
+ }
51
+ async batchInsert(metrics) {
52
+ await this.db("metrics").insert(metrics).onConflict("day").ignore();
53
+ }
54
+ async getMostRecentDayFromMetrics() {
55
+ try {
56
+ const mostRecent = await this.db("metrics").orderBy("day", "desc").first("day");
57
+ return mostRecent ? mostRecent.day : void 0;
58
+ } catch (e) {
59
+ return void 0;
60
+ }
61
+ }
62
+ }
63
+
64
+ class Scheduler {
65
+ constructor(options) {
66
+ this.options = options;
67
+ }
68
+ static create(options) {
69
+ return new Scheduler(options);
70
+ }
71
+ async run() {
72
+ try {
73
+ this.options.logger.info("Starting Github Copilot Processor");
74
+ const copilotMetrics = await this.options.api.getCopilotUsageDataForEnterprise();
75
+ this.options.logger.info(`Fetched ${copilotMetrics.length} metrics`);
76
+ const lastDay = await this.options.db.getMostRecentDayFromMetrics();
77
+ this.options.logger.info(`Found last day: ${lastDay}`);
78
+ const diff = copilotMetrics.sort(
79
+ (a, b) => luxon.DateTime.fromISO(a.day).toMillis() - luxon.DateTime.fromISO(b.day).toMillis()
80
+ ).filter((metric) => {
81
+ const metricDate = luxon.DateTime.fromISO(metric.day);
82
+ const lastDayDate = lastDay ? luxon.DateTime.fromISO(lastDay) : null;
83
+ return !lastDayDate || metricDate > lastDayDate;
84
+ }).map(({ breakdown, ...rest }) => ({
85
+ ...rest,
86
+ breakdown: JSON.stringify(breakdown)
87
+ }));
88
+ this.options.logger.info(`Found ${diff.length} new metrics to insert`);
89
+ if (diff.length > 0) {
90
+ await batchInsertInChunks(
91
+ diff,
92
+ 30,
93
+ async (chunk) => {
94
+ await this.options.db.batchInsert(chunk);
95
+ }
96
+ );
97
+ this.options.logger.info("Inserted new metrics into the database");
98
+ } else {
99
+ this.options.logger.info("No new metrics found to insert");
100
+ }
101
+ } catch (error) {
102
+ this.options.logger.error(
103
+ `An error occurred while processing Github Copilot metrics: ${error}`
104
+ );
105
+ }
106
+ }
107
+ }
108
+ async function batchInsertInChunks(data, chunkSize, batchInsertFunc) {
109
+ for (let i = 0; i < data.length; i += chunkSize) {
110
+ const chunk = data.slice(i, i + chunkSize);
111
+ await batchInsertFunc(chunk);
112
+ }
113
+ }
114
+
115
+ const getGithubInfo = async (config) => {
116
+ const integrations = integration.ScmIntegrations.fromConfig(config);
117
+ const credentialsProvider = integration.DefaultGithubCredentialsProvider.fromIntegrations(integrations);
118
+ const host = config.getString("copilot.host");
119
+ const enterprise = config.getString("copilot.enterprise");
120
+ if (!host) {
121
+ throw new Error("The host configuration is missing from the config.");
122
+ }
123
+ if (!enterprise) {
124
+ throw new Error("The enterprise configuration is missing from the config.");
125
+ }
126
+ const githubConfig = integrations.github.byHost(host)?.config;
127
+ if (!githubConfig) {
128
+ throw new Error(
129
+ `GitHub configuration for host "${host}" is missing or incomplete.`
130
+ );
131
+ }
132
+ const apiBaseUrl = githubConfig.apiBaseUrl ?? "https://api.github.com";
133
+ const credentials = await credentialsProvider.getCredentials({
134
+ url: apiBaseUrl
135
+ });
136
+ if (!credentials.headers) {
137
+ throw new Error("Failed to retrieve credentials headers.");
138
+ }
139
+ return {
140
+ apiBaseUrl,
141
+ credentials,
142
+ enterprise
143
+ };
144
+ };
145
+
146
+ class GithubClient {
147
+ constructor(props) {
148
+ this.props = props;
149
+ }
150
+ static async fromConfig(config) {
151
+ const info = await getGithubInfo(config);
152
+ return new GithubClient(info);
153
+ }
154
+ async getCopilotUsageDataForEnterprise() {
155
+ const path = `/enterprises/${this.props.enterprise}/copilot/usage`;
156
+ return this.get(path);
157
+ }
158
+ async get(path) {
159
+ const response = await fetch__default.default(`${this.props.apiBaseUrl}${path}`, {
160
+ headers: {
161
+ ...this.props.credentials.headers,
162
+ Accept: "application/vnd.github+json",
163
+ "X-GitHub-Api-Version": "2022-11-28"
164
+ }
165
+ });
166
+ if (!response.ok) {
167
+ throw await errors.ResponseError.fromResponse(response);
168
+ }
169
+ return response.json();
170
+ }
171
+ }
172
+
173
+ const defaultSchedule = {
174
+ frequency: { cron: "0 2 * * *" },
175
+ timeout: { minutes: 15 },
176
+ initialDelay: { minutes: 1 },
177
+ scope: "local"
178
+ };
179
+ async function createRouterFromConfig(routerOptions) {
180
+ const { config } = routerOptions;
181
+ const pluginOptions = {
182
+ schedule: defaultSchedule
183
+ };
184
+ if (config && config.has("copilot.schedule")) {
185
+ pluginOptions.schedule = backendPluginApi.readSchedulerServiceTaskScheduleDefinitionFromConfig(
186
+ config.getConfig("copilot.schedule")
187
+ );
188
+ }
189
+ return createRouter(routerOptions, pluginOptions);
190
+ }
191
+ async function createRouter(routerOptions, pluginOptions) {
192
+ const { logger, database, scheduler, config } = routerOptions;
193
+ const { schedule } = pluginOptions;
194
+ const db = await DatabaseHandler.create({ database });
195
+ const api = await GithubClient.fromConfig(config);
196
+ await scheduler.scheduleTask({
197
+ id: "copilot-metrics",
198
+ ...schedule ?? defaultSchedule,
199
+ fn: async () => await Scheduler.create({ db, logger, api, config }).run()
200
+ });
201
+ const router = Router__default.default();
202
+ router.use(express__default.default.json());
203
+ router.get("/health", (_, response) => {
204
+ logger.info("PONG!");
205
+ response.json({ status: "ok" });
206
+ });
207
+ router.get("/metrics", async (request, response) => {
208
+ const { startDate, endDate } = request.query;
209
+ if (typeof startDate !== "string" || typeof endDate !== "string") {
210
+ return response.status(400).json("Invalid query parameters");
211
+ }
212
+ const parsedStartDate = luxon.DateTime.fromISO(startDate);
213
+ const parsedEndDate = luxon.DateTime.fromISO(endDate);
214
+ if (!parsedStartDate.isValid || !parsedEndDate.isValid) {
215
+ return response.status(400).json("Invalid date format");
216
+ }
217
+ const result = await db.getByPeriod(startDate, endDate);
218
+ const metrics = result.map((metric) => ({
219
+ ...metric,
220
+ breakdown: JSON.parse(metric.breakdown)
221
+ }));
222
+ return response.json(metrics);
223
+ });
224
+ router.get("/metrics/period-range", async (_, response) => {
225
+ const result = await db.getPeriodRange();
226
+ if (!result) {
227
+ return response.status(400).json("No available data");
228
+ }
229
+ return response.json(result);
230
+ });
231
+ router.use(rootHttpRouter.MiddlewareFactory.create({ config, logger }).error);
232
+ return router;
233
+ }
234
+
235
+ const copilotPlugin = backendPluginApi.createBackendPlugin({
236
+ pluginId: "copilot",
237
+ register(env) {
238
+ env.registerInit({
239
+ deps: {
240
+ httpRouter: backendPluginApi.coreServices.httpRouter,
241
+ logger: backendPluginApi.coreServices.logger,
242
+ database: backendPluginApi.coreServices.database,
243
+ scheduler: backendPluginApi.coreServices.scheduler,
244
+ config: backendPluginApi.coreServices.rootConfig
245
+ },
246
+ async init({ httpRouter, logger, database, scheduler, config }) {
247
+ httpRouter.use(
248
+ await createRouterFromConfig({
249
+ logger,
250
+ database,
251
+ scheduler,
252
+ config
253
+ })
254
+ );
255
+ httpRouter.addAuthPolicy({
256
+ path: "/health",
257
+ allow: "unauthenticated"
258
+ });
259
+ }
260
+ });
261
+ }
262
+ });
263
+
264
+ exports.createRouterFromConfig = createRouterFromConfig;
265
+ exports.default = copilotPlugin;
266
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/db/DatabaseHandler.ts","../src/task/Scheduler.ts","../src/utils/GithubUtils.ts","../src/client/GithubClient.ts","../src/service/router.ts","../src/plugin.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 getByPeriod(\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","/*\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","/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport {\n DefaultGithubCredentialsProvider,\n GithubCredentials,\n ScmIntegrations,\n} from '@backstage/integration';\n\nexport type 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 const credentialsProvider =\n DefaultGithubCredentialsProvider.fromIntegrations(integrations);\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 = await credentialsProvider.getCredentials({\n url: apiBaseUrl,\n });\n\n if (!credentials.headers) {\n throw new Error('Failed to retrieve credentials headers.');\n }\n\n return {\n apiBaseUrl,\n credentials,\n enterprise,\n };\n};\n","/*\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","/*\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.getByPeriod(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","/*\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":["resolvePackagePath","DateTime","ScmIntegrations","DefaultGithubCredentialsProvider","fetch","ResponseError","readSchedulerServiceTaskScheduleDefinitionFromConfig","Router","express","MiddlewareFactory","createBackendPlugin","coreServices"],"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,WACJ,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;;ACpEA,MAAqB,SAAU,CAAA;AAAA,EAC7B,YAA6B,OAAkB,EAAA;AAAlB,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA,CAAA;AAAA,GAAmB;AAAA,EAEhD,OAAO,OAAO,OAAkB,EAAA;AAC9B,IAAO,OAAA,IAAI,UAAU,OAAO,CAAA,CAAA;AAAA,GAC9B;AAAA,EAEA,MAAM,GAAM,GAAA;AACV,IAAI,IAAA;AACF,MAAK,IAAA,CAAA,OAAA,CAAQ,MAAO,CAAA,IAAA,CAAK,mCAAmC,CAAA,CAAA;AAE5D,MAAA,MAAM,cACJ,GAAA,MAAM,IAAK,CAAA,OAAA,CAAQ,IAAI,gCAAiC,EAAA,CAAA;AAC1D,MAAA,IAAA,CAAK,QAAQ,MAAO,CAAA,IAAA,CAAK,CAAW,QAAA,EAAA,cAAA,CAAe,MAAM,CAAU,QAAA,CAAA,CAAA,CAAA;AAEnE,MAAA,MAAM,OAAU,GAAA,MAAM,IAAK,CAAA,OAAA,CAAQ,GAAG,2BAA4B,EAAA,CAAA;AAClE,MAAA,IAAA,CAAK,OAAQ,CAAA,MAAA,CAAO,IAAK,CAAA,CAAA,gBAAA,EAAmB,OAAO,CAAE,CAAA,CAAA,CAAA;AAErD,MAAA,MAAM,OAAO,cACV,CAAA,IAAA;AAAA,QACC,CAAC,CAAA,EAAG,CACF,KAAAC,cAAA,CAAS,QAAQ,CAAE,CAAA,GAAG,CAAE,CAAA,QAAA,KACxBA,cAAS,CAAA,OAAA,CAAQ,CAAE,CAAA,GAAG,EAAE,QAAS,EAAA;AAAA,OACrC,CACC,OAAO,CAAU,MAAA,KAAA;AAChB,QAAA,MAAM,UAAa,GAAAA,cAAA,CAAS,OAAQ,CAAA,MAAA,CAAO,GAAG,CAAA,CAAA;AAC9C,QAAA,MAAM,WAAc,GAAA,OAAA,GAAUA,cAAS,CAAA,OAAA,CAAQ,OAAO,CAAI,GAAA,IAAA,CAAA;AAC1D,QAAO,OAAA,CAAC,eAAe,UAAa,GAAA,WAAA,CAAA;AAAA,OACrC,EACA,GAAI,CAAA,CAAC,EAAE,SAAW,EAAA,GAAG,MAAY,MAAA;AAAA,QAChC,GAAG,IAAA;AAAA,QACH,SAAA,EAAW,IAAK,CAAA,SAAA,CAAU,SAAS,CAAA;AAAA,OACnC,CAAA,CAAA,CAAA;AAEJ,MAAA,IAAA,CAAK,QAAQ,MAAO,CAAA,IAAA,CAAK,CAAS,MAAA,EAAA,IAAA,CAAK,MAAM,CAAwB,sBAAA,CAAA,CAAA,CAAA;AAErE,MAAI,IAAA,IAAA,CAAK,SAAS,CAAG,EAAA;AACnB,QAAM,MAAA,mBAAA;AAAA,UACJ,IAAA;AAAA,UACA,EAAA;AAAA,UACA,OAAO,KAAyB,KAAA;AAC9B,YAAA,MAAM,IAAK,CAAA,OAAA,CAAQ,EAAG,CAAA,WAAA,CAAY,KAAK,CAAA,CAAA;AAAA,WACzC;AAAA,SACF,CAAA;AACA,QAAK,IAAA,CAAA,OAAA,CAAQ,MAAO,CAAA,IAAA,CAAK,wCAAwC,CAAA,CAAA;AAAA,OAC5D,MAAA;AACL,QAAK,IAAA,CAAA,OAAA,CAAQ,MAAO,CAAA,IAAA,CAAK,gCAAgC,CAAA,CAAA;AAAA,OAC3D;AAAA,aACO,KAAO,EAAA;AACd,MAAA,IAAA,CAAK,QAAQ,MAAO,CAAA,KAAA;AAAA,QAClB,8DAA8D,KAAK,CAAA,CAAA;AAAA,OACrE,CAAA;AAAA,KACF;AAAA,GACF;AACF,CAAA;AAEA,eAAe,mBAAA,CACb,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,CAAA;AACzC,IAAA,MAAM,gBAAgB,KAAK,CAAA,CAAA;AAAA,GAC7B;AACF;;ACjEa,MAAA,aAAA,GAAgB,OAAO,MAAwC,KAAA;AAC1E,EAAM,MAAA,YAAA,GAAeC,2BAAgB,CAAA,UAAA,CAAW,MAAM,CAAA,CAAA;AACtD,EAAM,MAAA,mBAAA,GACJC,4CAAiC,CAAA,gBAAA,CAAiB,YAAY,CAAA,CAAA;AAEhE,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,EAAM,MAAA,WAAA,GAAc,MAAM,mBAAA,CAAoB,cAAe,CAAA;AAAA,IAC3D,GAAK,EAAA,UAAA;AAAA,GACN,CAAA,CAAA;AAED,EAAI,IAAA,CAAC,YAAY,OAAS,EAAA;AACxB,IAAM,MAAA,IAAI,MAAM,yCAAyC,CAAA,CAAA;AAAA,GAC3D;AAEA,EAAO,OAAA;AAAA,IACL,UAAA;AAAA,IACA,WAAA;AAAA,IACA,UAAA;AAAA,GACF,CAAA;AACF,CAAA;;AC1CO,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,MAAM,aAAA,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;;ACiBA,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,OAAA;AACT,CAAA,CAAA;AAaA,eAAsB,uBAAuB,aAA8B,EAAA;AACzE,EAAM,MAAA,EAAE,QAAW,GAAA,aAAA,CAAA;AACnB,EAAA,MAAM,aAA+B,GAAA;AAAA,IACnC,QAAU,EAAA,eAAA;AAAA,GACZ,CAAA;AACA,EAAA,IAAI,MAAU,IAAA,MAAA,CAAO,GAAI,CAAA,kBAAkB,CAAG,EAAA;AAC5C,IAAA,aAAA,CAAc,QACZ,GAAAC,qEAAA;AAAA,MACE,MAAA,CAAO,UAAU,kBAAkB,CAAA;AAAA,KACrC,CAAA;AAAA,GACJ;AACA,EAAO,OAAA,YAAA,CAAa,eAAe,aAAa,CAAA,CAAA;AAClD,CAAA;AAGA,eAAe,YAAA,CACb,eACA,aACyB,EAAA;AACzB,EAAA,MAAM,EAAE,MAAA,EAAQ,QAAU,EAAA,SAAA,EAAW,QAAW,GAAA,aAAA,CAAA;AAChD,EAAM,MAAA,EAAE,UAAa,GAAA,aAAA,CAAA;AAErB,EAAA,MAAM,KAAK,MAAM,eAAA,CAAgB,MAAO,CAAA,EAAE,UAAU,CAAA,CAAA;AACpD,EAAA,MAAM,GAAM,GAAA,MAAM,YAAa,CAAA,UAAA,CAAW,MAAM,CAAA,CAAA;AAEhD,EAAA,MAAM,UAAU,YAAa,CAAA;AAAA,IAC3B,EAAI,EAAA,iBAAA;AAAA,IACJ,GAAI,QAAY,IAAA,eAAA;AAAA,IAChB,EAAI,EAAA,YAAY,MAAM,SAAA,CAAU,MAAO,CAAA,EAAE,EAAI,EAAA,MAAA,EAAQ,GAAK,EAAA,MAAA,EAAQ,CAAA,CAAE,GAAI,EAAA;AAAA,GACzE,CAAA,CAAA;AAED,EAAA,MAAM,SAASC,uBAAO,EAAA,CAAA;AACtB,EAAO,MAAA,CAAA,GAAA,CAAIC,wBAAQ,CAAA,IAAA,EAAM,CAAA,CAAA;AAEzB,EAAA,MAAA,CAAO,GAAI,CAAA,SAAA,EAAW,CAAC,CAAA,EAAG,QAAa,KAAA;AACrC,IAAA,MAAA,CAAO,KAAK,OAAO,CAAA,CAAA;AACnB,IAAA,QAAA,CAAS,IAAK,CAAA,EAAE,MAAQ,EAAA,IAAA,EAAM,CAAA,CAAA;AAAA,GAC/B,CAAA,CAAA;AAED,EAAA,MAAA,CAAO,GAAI,CAAA,UAAA,EAAY,OAAO,OAAA,EAAS,QAAa,KAAA;AAClD,IAAA,MAAM,EAAE,SAAA,EAAW,OAAQ,EAAA,GAAI,OAAQ,CAAA,KAAA,CAAA;AAEvC,IAAA,IAAI,OAAO,SAAA,KAAc,QAAY,IAAA,OAAO,YAAY,QAAU,EAAA;AAChE,MAAA,OAAO,QAAS,CAAA,MAAA,CAAO,GAAG,CAAA,CAAE,KAAK,0BAA0B,CAAA,CAAA;AAAA,KAC7D;AAEA,IAAM,MAAA,eAAA,GAAkBP,cAAS,CAAA,OAAA,CAAQ,SAAS,CAAA,CAAA;AAClD,IAAM,MAAA,aAAA,GAAgBA,cAAS,CAAA,OAAA,CAAQ,OAAO,CAAA,CAAA;AAE9C,IAAA,IAAI,CAAC,eAAA,CAAgB,OAAW,IAAA,CAAC,cAAc,OAAS,EAAA;AACtD,MAAA,OAAO,QAAS,CAAA,MAAA,CAAO,GAAG,CAAA,CAAE,KAAK,qBAAqB,CAAA,CAAA;AAAA,KACxD;AAEA,IAAA,MAAM,MAAS,GAAA,MAAM,EAAG,CAAA,WAAA,CAAY,WAAW,OAAO,CAAA,CAAA;AAEtD,IAAM,MAAA,OAAA,GAAoB,MAAO,CAAA,GAAA,CAAI,CAAW,MAAA,MAAA;AAAA,MAC9C,GAAG,MAAA;AAAA,MACH,SAAW,EAAA,IAAA,CAAK,KAAM,CAAA,MAAA,CAAO,SAAS,CAAA;AAAA,KACtC,CAAA,CAAA,CAAA;AAEF,IAAO,OAAA,QAAA,CAAS,KAAK,OAAO,CAAA,CAAA;AAAA,GAC7B,CAAA,CAAA;AAED,EAAA,MAAA,CAAO,GAAI,CAAA,uBAAA,EAAyB,OAAO,CAAA,EAAG,QAAa,KAAA;AACzD,IAAM,MAAA,MAAA,GAAS,MAAM,EAAA,CAAG,cAAe,EAAA,CAAA;AAEvC,IAAA,IAAI,CAAC,MAAQ,EAAA;AACX,MAAA,OAAO,QAAS,CAAA,MAAA,CAAO,GAAG,CAAA,CAAE,KAAK,mBAAmB,CAAA,CAAA;AAAA,KACtD;AAEA,IAAO,OAAA,QAAA,CAAS,KAAK,MAAM,CAAA,CAAA;AAAA,GAC5B,CAAA,CAAA;AAED,EAAO,MAAA,CAAA,GAAA,CAAIQ,iCAAkB,MAAO,CAAA,EAAE,QAAQ,MAAO,EAAC,EAAE,KAAK,CAAA,CAAA;AAC7D,EAAO,OAAA,MAAA,CAAA;AACT;;AC1IO,MAAM,gBAAgBC,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,MAAM,sBAAuB,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;;;;;"}
@@ -0,0 +1,60 @@
1
+ import express from 'express';
2
+ import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
3
+ import { SchedulerServiceTaskScheduleDefinition, LoggerService, DatabaseService, SchedulerService } from '@backstage/backend-plugin-api';
4
+ import { Config } from '@backstage/config';
5
+
6
+ /**
7
+ * Options for configuring the Copilot plugin.
8
+ *
9
+ * @public
10
+ */
11
+ interface PluginOptions {
12
+ /**
13
+ * Schedule configuration for the plugin.
14
+ */
15
+ schedule?: SchedulerServiceTaskScheduleDefinition;
16
+ }
17
+ /**
18
+ * Options for configuring the router used by the Copilot plugin.
19
+ *
20
+ * @public
21
+ */
22
+ interface RouterOptions {
23
+ /**
24
+ * Logger service for the router.
25
+ */
26
+ logger: LoggerService;
27
+ /**
28
+ * Database service for the router.
29
+ */
30
+ database: DatabaseService;
31
+ /**
32
+ * Scheduler service for the router.
33
+ */
34
+ scheduler: SchedulerService;
35
+ /**
36
+ * Configuration for the router.
37
+ */
38
+ config: Config;
39
+ }
40
+ /**
41
+ * Creates an Express router configured based on the provided router options and plugin options.
42
+ *
43
+ * This function initializes the router with the appropriate middleware and routes based on the
44
+ * configuration and options provided. It also schedules tasks if scheduling options are provided.
45
+ *
46
+ * @param routerOptions - Options for configuring the router, including services and configuration.
47
+ * @returns A promise that resolves to an Express router instance.
48
+ *
49
+ * @public
50
+ */
51
+ declare function createRouterFromConfig(routerOptions: RouterOptions): Promise<express.Router>;
52
+
53
+ /**
54
+ * Backend plugin for Copilot.
55
+ *
56
+ * @public
57
+ */
58
+ declare const copilotPlugin: _backstage_backend_plugin_api.BackendFeatureCompat;
59
+
60
+ export { type PluginOptions, type RouterOptions, createRouterFromConfig, copilotPlugin as default };
@@ -0,0 +1,75 @@
1
+ /*
2
+ * Copyright 2021 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
+ // @ts-check
18
+
19
+ /**
20
+ * @param {import('knex').Knex} knex
21
+ */
22
+ exports.up = async function up(knex) {
23
+ await knex.schema.createTable('metrics', table => {
24
+ table.comment('Table for storing metrics');
25
+ table
26
+ .date('day')
27
+ .notNullable()
28
+ .primary()
29
+ .comment('Date of the metrics data');
30
+ table
31
+ .integer('total_suggestions_count')
32
+ .notNullable()
33
+ .comment('Total suggestions count for the day');
34
+ table
35
+ .integer('total_acceptances_count')
36
+ .notNullable()
37
+ .comment('Total acceptances count for the day');
38
+ table
39
+ .integer('total_lines_suggested')
40
+ .notNullable()
41
+ .comment('Total lines suggested for the day');
42
+ table
43
+ .integer('total_lines_accepted')
44
+ .notNullable()
45
+ .comment('Total lines accepted for the day');
46
+ table
47
+ .integer('total_active_users')
48
+ .notNullable()
49
+ .comment('Total active users for the day');
50
+ table
51
+ .integer('total_chat_acceptances')
52
+ .notNullable()
53
+ .comment('Total chat acceptances for the day');
54
+ table
55
+ .integer('total_chat_turns')
56
+ .notNullable()
57
+ .comment('Total chat turns for the day');
58
+ table
59
+ .integer('total_active_chat_users')
60
+ .notNullable()
61
+ .comment('Total active chat users for the day');
62
+ table.text('breakdown').notNullable().comment('Breakdown information');
63
+ });
64
+
65
+ await knex.schema.alterTable('metrics', table => {
66
+ table.index('day', 'idx_metrics_day');
67
+ });
68
+ };
69
+
70
+ /**
71
+ * @param {import('knex').Knex} knex
72
+ */
73
+ exports.down = async function down(knex) {
74
+ await knex.schema.dropTable('metrics');
75
+ };
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@backstage-community/plugin-copilot-backend",
3
+ "version": "0.1.0",
4
+ "homepage": "https://backstage.io",
5
+ "license": "Apache-2.0",
6
+ "main": "dist/index.cjs.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "config.d.ts",
11
+ "migrations/**/*.{js,d.ts}"
12
+ ],
13
+ "backstage": {
14
+ "role": "backend-plugin",
15
+ "pluginId": "copilot",
16
+ "pluginPackages": [
17
+ "@backstage-community/plugin-copilot",
18
+ "@backstage-community/plugin-copilot-backend",
19
+ "@backstage-community/plugin-copilot-common"
20
+ ]
21
+ },
22
+ "publishConfig": {
23
+ "access": "public",
24
+ "main": "dist/index.cjs.js",
25
+ "types": "dist/index.d.ts"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/backstage/community-plugins",
30
+ "directory": "workspaces/copilot/plugins/copilot-backend"
31
+ },
32
+ "scripts": {
33
+ "start": "backstage-cli package start",
34
+ "build": "backstage-cli package build",
35
+ "lint": "backstage-cli package lint",
36
+ "test": "backstage-cli package test",
37
+ "clean": "backstage-cli package clean",
38
+ "prepack": "backstage-cli package prepack",
39
+ "postpack": "backstage-cli package postpack"
40
+ },
41
+ "dependencies": {
42
+ "@backstage-community/plugin-copilot-common": "^0.1.0",
43
+ "@backstage/backend-app-api": "^0.8.0",
44
+ "@backstage/backend-common": "^0.23.3",
45
+ "@backstage/backend-defaults": "^0.4.1",
46
+ "@backstage/backend-plugin-api": "^0.7.0",
47
+ "@backstage/backend-tasks": "^0.5.27",
48
+ "@backstage/config": "^1.2.0",
49
+ "@backstage/errors": "^1.2.4",
50
+ "@backstage/integration": "^1.13.0",
51
+ "@types/express": "*",
52
+ "express": "^4.17.1",
53
+ "express-promise-router": "^4.1.0",
54
+ "knex": "^3.0.0",
55
+ "luxon": "^3.5.0",
56
+ "node-fetch": "^2.6.7",
57
+ "winston": "^3.2.1",
58
+ "yn": "^4.0.0"
59
+ },
60
+ "devDependencies": {
61
+ "@backstage/backend-test-utils": "^0.4.4",
62
+ "@backstage/cli": "^0.26.11",
63
+ "@backstage/plugin-auth-backend": "^0.22.9",
64
+ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.8",
65
+ "@backstage/test-utils": "^1.5.9",
66
+ "@types/node-fetch": "^2.6.11",
67
+ "@types/supertest": "^2.0.8",
68
+ "msw": "^1.0.0",
69
+ "supertest": "^6.2.4"
70
+ },
71
+ "configSchema": "config.d.ts"
72
+ }